Merge branch 'master' of github.com:webnotes/erpnext

This commit is contained in:
Priya 2013-09-02 10:15:00 +05:30
commit 5a7328a005
165 changed files with 6022 additions and 1404 deletions

View File

@ -1,21 +1,22 @@
[ [
{ {
"owner": "Administrator",
"docstatus": 0,
"creation": "2011-12-21 11:08:55", "creation": "2011-12-21 11:08:55",
"docstatus": 0,
"modified": "2013-08-16 16:15:46",
"modified_by": "Administrator", "modified_by": "Administrator",
"modified": "2012-03-20 12:29:49" "owner": "Administrator"
}, },
{ {
"doc_type": "Sales Invoice", "doc_type": "Sales Invoice",
"name": "__common__",
"module": "Accounts",
"doctype": "Print Format", "doctype": "Print Format",
"html": "<html>\n<head>\n<!--Other charges function-->\n<script>\nvar make_row = function(title,val,bold){\nvar bstart = '<b>'; var bend = '</b>';\nreturn '<tr><td style=\"width:50%\">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'\n +'<td style=\"width:20%\">'+doc.currency+'</td>'\n +'<td style=\"width:30%;text-align:right\">'+(val?val:'0.00')+'</td>'\n +'</tr>'\n}\nvar make_row1 = function(title,val,bold){\n var bstart = '<b>'; var bend = '</b>';\n \n return '<tr style=\"font-family:courier new; line-height:150%\"><td style=\"width:50%\">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'\n +'<td style=\"width:50%;text-align:right\">'+(bold?bstart:'')+(val?val:'0.00')+(bold?bend:'')+'</td>'\n +'</tr>'\n}\n\nfunction get_letter_head() {\n // add letter head\n var cp = wn.boot.control_panel;\n if(doc.letter_head)\n var lh= cstr(_p.letter_heads[doc.letter_head]);\n else if(cp.letter_head)\n var lh= cp.letter_head;\n else \n var lh= '';\n \n return lh;\n}\n\nfunction get_tax_details(){\n var cl = getchildren('Sales Taxes and Charges',doc.name,'other_charges');\n var out = '';\n\n out += '<div><table style=\"float: right;\">';\n for(var i=0;i<cl.length;i++){\n if(cl[i].tax_amount) {\n out += make_row1(cl[i].description,fmt_money(convert_rate(cl[i].tax_amount)),0);\n }\n }\n out += make_row1('TOTAL',doc.currency + \" \" + fmt_money(convert_rate(doc.grand_total)),1);\n out +='</table></div>';\n return out;\n}\n\nfunction convert_rate(val){ \n var new_val = flt(val)/flt(doc.conversion_rate);\n return new_val;\n}\n</script>\n<style>\n table, td, tr, div, span {\n font-family: courier new;\n line-height: 200%;\n }\n</style>\n</head>\n\n<body>\n<table width=\"100%\" style=\"font-family: courier new; line-height:200%\">\n<tr>\n <td align=\"left\">NO: <script>doc.name</script></td>\n <td align=\"right\">DATE: <script>date.str_to_user(doc.posting_date)</script></td>\n</tr>\n<tr>\n <td>M/s <script>doc.contact_display</script></td>\n</tr>\n</table>\n<!--Item Table-->\n<div>\n<script>\nvar t = print_table('Sales Invoice', doc.name, 'entries', 'Sales Invoice Item',\n ['description','qty','export_rate','export_amount'], ['ITEM', 'QTY','RATE','AMOUNT'],\n ['35%','20%','20%','25%']);\nif(t.appendChild) { // single\n out = t.innerHTML.replace(/style=\"/gi,'style=\"font-family:courier new;line-height:150%;');\n} else { //multiple\n out = '<table class=\"None\" border=\"0px\" width=\"100%\" style=\"border:0px; font-family:courier-new\">';\n\n for(var i=0;i<t.length;i++) {\n if(i==0) {\n out += '<tr>' + t[i].childNodes[0].childNodes[0].childNodes[0].innerHTML\n .replace(/style=\"border: 1px solid rgb\\(0, 0, 0\\);/gi,'style=\"font-family:courier new;')\n + '</tr>';\n }\n out += '<tr>' + t[i].childNodes[0].childNodes[0].childNodes[1].innerHTML\n .replace(/style=\"border: 1px solid rgb\\(0, 0, 0\\);/gi,'style=\"font-family:courier new;')\n + '</tr>';\n }\n out += '</table>';\n}\nout;\n</script>\n</div>\n<!--Tax table-->\n<div><script>get_tax_details();</script></div><br />\n<table style=\"font-family:courier new;\">\n <tr><td><b><script>doc.terms</script></b></td></tr>\n <tr><td nowrap><b>For <script>doc.company</script></b></td></tr>\n <tr><td>&nbsp;</td></tr>\n <tr><td nowrap><b>Signatory</b></td></tr>\n</table>\n</body>\n</html>\n", "html": "<html>\n<head>\n<!--Other charges function-->\n<script>\nvar make_row = function(title,val,bold){\nvar bstart = '<b>'; var bend = '</b>';\nreturn '<tr><td style=\"width:50%\">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'\n +'<td style=\"width:20%\">'+doc.currency+'</td>'\n +'<td style=\"width:30%;text-align:right\">'+(val?val:'0.00')+'</td>'\n +'</tr>'\n}\nvar make_row1 = function(title,val,bold){\n var bstart = '<b>'; var bend = '</b>';\n \n return '<tr style=\"font-family:courier new; line-height:150%\"><td style=\"width:50%\">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'\n +'<td style=\"width:50%;text-align:right\">'+(bold?bstart:'')+(val?val:'0.00')+(bold?bend:'')+'</td>'\n +'</tr>'\n}\n\nfunction get_letter_head() {\n // add letter head\n var cp = wn.boot.control_panel;\n if(doc.letter_head)\n var lh= cstr(_p.letter_heads[doc.letter_head]);\n else if(cp.letter_head)\n var lh= cp.letter_head;\n else \n var lh= '';\n \n return lh;\n}\n\nfunction get_tax_details(){\n var cl = getchildren('Sales Taxes and Charges',doc.name,'other_charges');\n var out = '';\n\n out += '<div><table style=\"float: right;\">';\n for(var i=0;i<cl.length;i++){\n if(cl[i].tax_amount) {\n out += make_row1(cl[i].description,fmt_money(convert_rate(cl[i].tax_amount)),0);\n }\n }\n out += make_row1('TOTAL',doc.currency + \" \" + fmt_money(convert_rate(doc.grand_total)),1);\n out +='</table></div>';\n return out;\n}\n\nfunction convert_rate(val){ \n var new_val = flt(val)/flt(doc.conversion_rate);\n return new_val;\n}\n</script>\n<style>\n table, td, tr, div, span {\n font-family: courier new;\n line-height: 200%;\n }\n</style>\n</head>\n\n<body>\n<table width=\"100%\" style=\"font-family: courier new; line-height:200%\">\n<tr>\n <td align=\"left\">NO: <script>doc.name</script></td>\n <td align=\"right\">DATE: <script>date.str_to_user(doc.posting_date)</script></td>\n</tr>\n<tr>\n <td>M/s <script>doc.contact_display</script></td>\n</tr>\n</table>\n<!--Item Table-->\n<div>\n<script>\nvar t = print_table(\n \t\t\t'Sales Invoice',\n\t\t\t\tdoc.name,\n\t\t\t\t'entries',\n\t\t\t\t'Sales Invoice Item',\n\t\t\t\t[// Here specify the table columns to be displayed\n\t\t\t\t\t'SR', 'description', 'qty', 'export_rate', 'export_amount'\n\t\t\t\t],\n\t\t\t\t[// Here specify the labels of column headings\n\t\t\t\t\t'Sr', 'Description', 'Qty', 'Rate', 'Amount'\n\t\t\t\t],\n\t\t\t\t[// Here specify the column widths\n\t\t\t\t\t'5%', '35%', '20%', '20%', '20%'\n\t\t\t\t],\n\t\t\t\tnull,\n\t\t\t\tnull\n\t\t\t);\nif(t.appendChild) { // single\n out = t.innerHTML.replace(/style=\"/gi,'style=\"font-family:courier new;line-height:150%;');\n} else { //multiple\n out = '<table class=\"None\" border=\"0px\" width=\"100%\" style=\"border:0px; font-family:courier-new\">';\n\n for(var i=0;i<t.length;i++) {\n if(i==0) {\n out += '<tr>' + t[i].childNodes[0].childNodes[0].childNodes[0].innerHTML\n .replace(/style=\"border: 1px solid rgb\\(0, 0, 0\\);/gi,'style=\"font-family:courier new;')\n + '</tr>';\n }\n out += '<tr>' + t[i].childNodes[0].childNodes[0].childNodes[1].innerHTML\n .replace(/style=\"border: 1px solid rgb\\(0, 0, 0\\);/gi,'style=\"font-family:courier new;')\n + '</tr>';\n }\n out += '</table>';\n}\nout;\n</script>\n</div>\n<!--Tax table-->\n<div><script>get_tax_details();</script></div><br />\n<table style=\"font-family:courier new;\">\n <tr><td><b><script>doc.terms</script></b></td></tr>\n <tr><td nowrap><b>For <script>doc.company</script></b></td></tr>\n <tr><td>&nbsp;</td></tr>\n <tr><td nowrap><b>Signatory</b></td></tr>\n</table>\n</body>\n</html>\n",
"module": "Accounts",
"name": "__common__",
"print_format_type": "Client",
"standard": "Yes" "standard": "Yes"
}, },
{ {
"name": "POS Invoice", "doctype": "Print Format",
"doctype": "Print Format" "name": "POS Invoice"
} }
] ]

View File

@ -231,11 +231,12 @@ class DocType(AccountsController):
for d in self.doclist.get({"parentfield": "entries"}): for d in self.doclist.get({"parentfield": "entries"}):
if d.against_invoice and webnotes.conn.get_value("Sales Invoice", if d.against_invoice and webnotes.conn.get_value("Sales Invoice",
d.against_invoice, "debit_to") != d.account: d.against_invoice, "debit_to") != d.account:
msgprint("Debit account is not matching with Sales Invoice", raise_exception=1) webnotes.throw(_("Credited account (Customer) is not matching with Sales Invoice"))
if d.against_voucher and webnotes.conn.get_value("Purchase Invoice", if d.against_voucher and webnotes.conn.get_value("Purchase Invoice",
d.against_voucher, "credit_to") != d.account: d.against_voucher, "credit_to") != d.account:
msgprint("Credit account is not matching with Purchase Invoice", raise_exception=1) webnotes.throw(_("Debited account (Supplier) is not matching with \
Purchase Invoice"))
def make_gl_entries(self, cancel=0, adv_adj=0): def make_gl_entries(self, cancel=0, adv_adj=0):
from accounts.general_ledger import make_gl_entries from accounts.general_ledger import make_gl_entries
@ -338,10 +339,71 @@ def get_default_bank_cash_account(company, voucher_type):
account = webnotes.conn.get_value("Company", company, account = webnotes.conn.get_value("Company", company,
voucher_type=="Bank Voucher" and "default_bank_account" or "default_cash_account") voucher_type=="Bank Voucher" and "default_bank_account" or "default_cash_account")
if account: if account:
return [{ return {
"account": account, "account": account,
"balance": get_balance_on(account) "balance": get_balance_on(account)
}] }
@webnotes.whitelist()
def get_payment_entry_from_sales_invoice(sales_invoice):
from accounts.utils import get_balance_on
si = webnotes.bean("Sales Invoice", sales_invoice)
jv = get_payment_entry(si.doc)
jv.doc.remark = 'Payment received against Sales Invoice %(name)s. %(remarks)s' % si.doc.fields
# credit customer
jv.doclist[1].account = si.doc.debit_to
jv.doclist[1].balance = get_balance_on(si.doc.debit_to)
jv.doclist[1].credit = si.doc.outstanding_amount
jv.doclist[1].against_invoice = si.doc.name
# debit bank
jv.doclist[2].debit = si.doc.outstanding_amount
return [d.fields for d in jv.doclist]
@webnotes.whitelist()
def get_payment_entry_from_purchase_invoice(purchase_invoice):
from accounts.utils import get_balance_on
pi = webnotes.bean("Purchase Invoice", purchase_invoice)
jv = get_payment_entry(pi.doc)
jv.doc.remark = 'Payment against Purchase Invoice %(name)s. %(remarks)s' % pi.doc.fields
# credit supplier
jv.doclist[1].account = pi.doc.credit_to
jv.doclist[1].balance = get_balance_on(pi.doc.credit_to)
jv.doclist[1].debit = pi.doc.outstanding_amount
jv.doclist[1].against_voucher = pi.doc.name
# credit bank
jv.doclist[2].credit = pi.doc.outstanding_amount
return [d.fields for d in jv.doclist]
def get_payment_entry(doc):
bank_account = get_default_bank_cash_account(doc.company, "Bank Voucher")
jv = webnotes.new_bean('Journal Voucher')
jv.doc.voucher_type = 'Bank Voucher'
jv.doc.company = doc.company
jv.doc.fiscal_year = doc.fiscal_year
jv.doclist.append({
"doctype": "Journal Voucher Detail",
"parentfield": "entries"
})
jv.doclist.append({
"doctype": "Journal Voucher Detail",
"parentfield": "entries"
})
if bank_account:
jv.doclist[2].account = bank_account["account"]
jv.doclist[2].balance = bank_account["balance"]
return jv
@webnotes.whitelist() @webnotes.whitelist()
def get_opening_accounts(company): def get_opening_accounts(company):

View File

@ -106,13 +106,13 @@ cur_frm.cscript.is_opening = function(doc, dt, dn) {
cur_frm.cscript.make_bank_voucher = function() { cur_frm.cscript.make_bank_voucher = function() {
return wn.call({ return wn.call({
method: "accounts.doctype.journal_voucher.journal_voucher.get_default_bank_cash_account", method: "accounts.doctype.journal_voucher.journal_voucher.get_payment_entry_from_purchase_invoice",
args: { args: {
"company": cur_frm.doc.company, "purchase_invoice": cur_frm.doc.name,
"voucher_type": "Bank Voucher"
}, },
callback: function(r) { callback: function(r) {
cur_frm.cscript.make_jv(cur_frm.doc, null, null, r.message); var doclist = wn.model.sync(r.message);
wn.set_route("Form", doclist[0].doctype, doclist[0].name);
} }
}); });
} }
@ -198,31 +198,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn){
refresh_field('entries'); refresh_field('entries');
} }
cur_frm.cscript.make_jv = function(doc, dt, dn, bank_account) {
var jv = wn.model.make_new_doc_and_get_name('Journal Voucher');
jv = locals['Journal Voucher'][jv];
jv.voucher_type = 'Bank Voucher';
jv.remark = repl('Payment against voucher %(vn)s for %(rem)s', {vn:doc.name, rem:doc.remarks});
jv.total_debit = doc.outstanding_amount;
jv.total_credit = doc.outstanding_amount;
jv.fiscal_year = doc.fiscal_year;
jv.company = doc.company;
// debit to creditor
var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries');
d1.account = doc.credit_to;
d1.debit = doc.outstanding_amount;
d1.against_voucher = doc.name;
// credit to bank
var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries');
d1.account = bank_account.account;
d1.credit = doc.outstanding_amount;
d1.balance = bank_account.balance;
loaddoc('Journal Voucher', jv.name);
}
cur_frm.fields_dict['entries'].grid.get_field('project_name').get_query = function(doc, cdt, cdn) { cur_frm.fields_dict['entries'].grid.get_field('project_name').get_query = function(doc, cdt, cdn) {
return{ return{
filters:[ filters:[

View File

@ -127,7 +127,7 @@ class DocType(BuyingController):
if not self.doc.remarks and self.doc.bill_date: if not self.doc.remarks and self.doc.bill_date:
self.doc.remarks = (self.doc.remarks or '') + "\n" + ("Against Bill %s dated %s" self.doc.remarks = (self.doc.remarks or '') + "\n" + ("Against Bill %s dated %s"
% (self.doc.bill_no, formatdate(self.doc.bill_date))) % (self.doc.bill_no, formatdate(self.doc.bill_date)))
else:
if not self.doc.remarks: if not self.doc.remarks:
self.doc.remarks = "No Remarks" self.doc.remarks = "No Remarks"
@ -261,13 +261,11 @@ class DocType(BuyingController):
if d.purchase_order: if d.purchase_order:
submitted = sql("select name from `tabPurchase Order` where docstatus = 1 and name = '%s'" % d.purchase_order) submitted = sql("select name from `tabPurchase Order` where docstatus = 1 and name = '%s'" % d.purchase_order)
if not submitted: if not submitted:
msgprint("Purchase Order : "+ cstr(d.purchase_order) +" is not submitted") webnotes.throw("Purchase Order : "+ cstr(d.purchase_order) +" is not submitted")
raise Exception , "Validation Error."
if d.purchase_receipt: if d.purchase_receipt:
submitted = sql("select name from `tabPurchase Receipt` where docstatus = 1 and name = '%s'" % d.purchase_receipt) submitted = sql("select name from `tabPurchase Receipt` where docstatus = 1 and name = '%s'" % d.purchase_receipt)
if not submitted: if not submitted:
msgprint("Purchase Receipt : "+ cstr(d.purchase_receipt) +" is not submitted") webnotes.throw("Purchase Receipt : "+ cstr(d.purchase_receipt) +" is not submitted")
raise Exception , "Validation Error."
def update_against_document_in_jv(self): def update_against_document_in_jv(self):

View File

@ -5,9 +5,72 @@ erpnext.POS = Class.extend({
init: function(wrapper, frm) { init: function(wrapper, frm) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.frm = frm; this.frm = frm;
this.wrapper.html('<div class="customer-area"></div>\ this.wrapper.html('<div class="container">\
<div class="item-area"></div>\ <div class="row">\
<div><button class="btn btn-default btn-add">Add</button>'); <div class="customer-area col-sm-3 col-xs-6"></div>\
<div class="barcode-area col-sm-3 col-xs-6"></div>\
<div class="search-area col-sm-3 col-xs-6"></div>\
<div class="item-group-area col-sm-3 col-xs-6"></div>\
</div>\
<div class="row">\
<div class="col-sm-6">\
<div class="pos-bill">\
<div class="item-cart">\
<table class="table table-condensed table-hover" id="cart" style="table-layout: fixed;">\
<thead>\
<tr>\
<th style="width: 50%">Item</th>\
<th style="width: 25%; text-align: right;">Qty</th>\
<th style="width: 25%; text-align: right;">Rate</th>\
</tr>\
</thead>\
<tbody>\
</tbody>\
</table>\
</div>\
<br>\
<div class="net-total-area" style="margin-left: 40%;">\
<table class="table table-condensed">\
<tr>\
<td><b>Net Total</b></td>\
<td style="text-align: right;" class="net-total"></td>\
</tr>\
</table>\
<div class="tax-table" style="display: none;">\
<table class="table table-condensed">\
<thead>\
<tr>\
<th style="width: 60%">Taxes</th>\
<th style="width: 40%; text-align: right;"></th>\
</tr>\
</thead>\
<tbody>\
</tbody>\
</table>\
</div>\
<table class="table table-condensed">\
<tr>\
<td style="vertical-align: middle;"><b>Grand Total</b></td>\
<td style="text-align: right; font-size: 200%; \
font-size: bold;" class="grand-total"></td>\
</tr>\
</table>\
</div>\
</div>\
<br><br>\
<button class="btn btn-success btn-lg make-payment">\
<i class="icon-money"></i> Make Payment</button>\
<button class="btn btn-default btn-lg delete-items pull-right" style="display: none;">\
<i class="icon-trash"></i> Del</button>\
<br><br>\
</div>\
<div class="col-sm-6">\
<div class="item-list-area">\
<div class="col-sm-12">\
<div class="row item-list"></div></div>\
</div>\
</div>\
</div></div>');
this.make(); this.make();
@ -16,10 +79,20 @@ erpnext.POS = Class.extend({
me.refresh(); me.refresh();
}); });
this.wrapper.find(".delete-items").on("click", function() {
me.remove_selected_item();
});
this.wrapper.find(".make-payment").on("click", function() {
me.make_payment();
});
}, },
make: function() { make: function() {
this.make_customer(); this.make_customer();
this.make_items(); this.make_item_group();
this.make_search();
this.make_barcode();
this.make_item_list();
}, },
make_customer: function() { make_customer: function() {
var me = this; var me = this;
@ -28,7 +101,8 @@ erpnext.POS = Class.extend({
"fieldtype": "Link", "fieldtype": "Link",
"options": "Customer", "options": "Customer",
"label": "Customer", "label": "Customer",
"fieldname": "pos_customer" "fieldname": "pos_customer",
"placeholder": "Customer"
}, },
parent: this.wrapper.find(".customer-area") parent: this.wrapper.find(".customer-area")
}); });
@ -38,23 +112,300 @@ erpnext.POS = Class.extend({
wn.model.set_value("Sales Invoice", me.frm.docname, "customer", this.value); wn.model.set_value("Sales Invoice", me.frm.docname, "customer", this.value);
}); });
}, },
make_items: function() { make_item_group: function() {
var me = this; var me = this;
this.wrapper.find(".btn-add").click(function() { this.item_group = wn.ui.form.make_control({
var child = wn.model.add_child(me.frm.doc, "Sales Invoice Item", "entries"); df: {
child.item_code = "Test Item"; "fieldtype": "Link",
me.frm.cscript.item_code(me.frm.doc, child.doctype, child.name); "options": "Item Group",
"label": "Item Group",
"fieldname": "pos_item_group",
"placeholder": "Filter by Item Group"
},
parent: this.wrapper.find(".item-group-area")
}); });
this.item_group.make_input();
this.item_group.$input.on("change", function() {
if(!me.item_group.autocomplete_open)
me.make_item_list();
});
},
make_search: function() {
var me = this;
this.search = wn.ui.form.make_control({
df: {
"fieldtype": "Link",
"options": "Item",
"label": "Item",
"fieldname": "pos_item",
"placeholder": "Select Item"
},
parent: this.wrapper.find(".search-area")
});
this.search.make_input();
this.search.$input.on("change", function() {
if(!me.search.autocomplete_open)
me.make_item_list();
});
},
make_barcode: function() {
var me = this;
this.barcode = wn.ui.form.make_control({
df: {
"fieldtype": "Data",
"label": "Barcode",
"fieldname": "pos_barcode",
"placeholder": "Select Barcode"
},
parent: this.wrapper.find(".barcode-area")
});
this.barcode.make_input();
this.barcode.$input.on("change", function() {
me.add_item_thru_barcode();
});
},
make_item_list: function() {
var me = this;
wn.call({
method: 'accounts.doctype.sales_invoice.pos.get_items',
args: {
price_list: cur_frm.doc.selling_price_list,
item_group: this.item_group.$input.val(),
item: this.search.$input.val()
},
callback: function(r) {
var $wrap = me.wrapper.find(".item-list");
me.wrapper.find(".item-list").empty();
$.each(r.message, function(index, obj) {
if (obj.image)
image = "<img src='" + obj.image + "' class='img-responsive'>";
else
image = '<div class="missing-image"><i class="icon-camera"></i></div>';
$(repl('<div class="col-xs-3 pos-item" data-item_code="%(item_code)s">\
%(item_image)s\
<div class="small">%(item_code)s</div>\
<div class="small">%(item_name)s</div>\
<div class="small">%(item_price)s</div>\
</div>',
{
item_code: obj.name,
item_price: format_currency(obj.ref_rate, obj.ref_currency),
item_name: obj.name===obj.item_name ? "" : obj.item_name,
item_image: image
})).appendTo($wrap);
});
$("div.pos-item").on("click", function() {
if(!cur_frm.doc.customer) {
msgprint("Please select customer first.");
return;
}
me.add_to_cart($(this).attr("data-item_code"));
});
}
});
},
add_to_cart: function(item_code) {
var me = this;
var caught = false;
// get no_of_items
no_of_items = me.wrapper.find("#cart tbody").length;
// check whether the item is already added
if (no_of_items != 0) {
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
"Sales Invoice"), function(i, d) {
if (d.item_code == item_code)
caught = true;
});
}
// if duplicate row then append the qty
if (caught) {
me.update_qty(item_code, 1);
}
else {
var child = wn.model.add_child(me.frm.doc, "Sales Invoice Item", "entries");
child.item_code = item_code;
me.frm.cscript.item_code(me.frm.doc, child.doctype, child.name);
//me.refresh();
}
},
update_qty: function(item_code, qty) {
var me = this;
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
"Sales Invoice"), function(i, d) {
if (d.item_code == item_code) {
if (qty == 1)
d.qty += 1;
else
d.qty = qty;
me.frm.cscript.qty(me.frm.doc, d.doctype, d.name);
}
});
me.refresh();
}, },
refresh: function() { refresh: function() {
var me = this; var me = this;
this.customer.set_input(this.frm.doc.customer); this.customer.set_input(this.frm.doc.customer);
this.barcode.set_input("");
// add items // add items
var $items = me.wrapper.find(".item-area").empty(); var $items = me.wrapper.find("#cart tbody").empty();
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries", $.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
"Sales Invoice"), function(i, d) { "Sales Invoice"), function(i, d) {
$(repl("<div>%(item_code)s</div>", d)).appendTo($items); $(repl('<tr id="%(item_code)s" data-selected="false">\
<td>%(item_code)s%(item_name)s</td>\
<td><input type="text" value="%(qty)s" \
class="form-control qty" style="text-align: right;"></td>\
<td style="text-align: right;">%(rate)s<br><b>%(amount)s</b></td>\
</tr>',
{
item_code: d.item_code,
item_name: d.item_name===d.item_code ? "" : ("<br>" + d.item_name),
qty: d.qty,
rate: format_currency(d.ref_rate, cur_frm.doc.price_list_currency),
amount: format_currency(d.export_amount, cur_frm.doc.price_list_currency)
}
)).appendTo($items);
});
// taxes
var taxes = wn.model.get_children("Sales Taxes and Charges", this.frm.doc.name, "other_charges",
"Sales Invoice");
$(".tax-table")
.toggle((taxes && taxes.length) ? true : false)
.find("tbody").empty();
$.each(taxes, function(i, d) {
$(repl('<tr>\
<td>%(description)s</td>\
<td style="text-align: right;">%(tax_amount)s</td>\
<tr>', {
description: d.description,
tax_amount: format_currency(d.tax_amount, me.frm.doc.price_list_currency)
})).appendTo(".tax-table tbody");
});
// set totals
this.wrapper.find(".net-total").text(format_currency(this.frm.doc.net_total_export,
cur_frm.doc.price_list_currency));
this.wrapper.find(".grand-total").text(format_currency(this.frm.doc.grand_total_export,
cur_frm.doc.price_list_currency));
// append quantity to the respective item after change from input box
$("input.qty").on("change", function() {
var item_code = $(this).closest("tr")[0].id;
me.update_qty(item_code, $(this).val());
});
// on td click highlight the respective row
$("td").on("click", function() {
var row = $(this).closest("tr");
if (row.attr("data-selected") == "false") {
row.attr("class", "warning");
row.attr("data-selected", "true");
}
else {
row.prop("class", null);
row.attr("data-selected", "false");
}
me.refresh_delete_btn();
});
me.refresh_delete_btn();
},
refresh_delete_btn: function() {
$(".delete-items").toggle($(".item-cart .warning").length ? true : false);
},
add_item_thru_barcode: function() {
var me = this;
wn.call({
method: 'accounts.doctype.sales_invoice.pos.get_item_from_barcode',
args: {barcode: this.barcode.$input.val()},
callback: function(r) {
if (r.message) {
me.add_to_cart(r.message[0].name);
me.refresh();
}
else
msgprint(wn._("Invalid Barcode"));
}
});
},
remove_selected_item: function() {
var me = this;
var selected_items = [];
var no_of_items = $("#cart tbody tr").length;
for(var x=0; x<=no_of_items - 1; x++) {
var row = $("#cart tbody tr:eq(" + x + ")");
if(row.attr("data-selected") == "true") {
selected_items.push(row.attr("id"));
}
}
if (!selected_items[0])
msgprint(wn._("Please select any item to remove it"));
var child = wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
"Sales Invoice");
$.each(child, function(i, d) {
for (var i in selected_items) {
if (d.item_code == selected_items[i]) {
wn.model.clear_doc(d.doctype, d.name);
}
}
});
cur_frm.fields_dict["entries"].grid.refresh();
me.refresh();
},
make_payment: function() {
var me = this;
var no_of_items = $("#cart tbody tr").length;
var mode_of_payment = [];
if (no_of_items == 0)
msgprint(wn._("Payment cannot be made for empty cart"));
else {
wn.call({
method: 'accounts.doctype.sales_invoice.pos.get_mode_of_payment',
callback: function(r) {
for (x=0; x<=r.message.length - 1; x++) {
mode_of_payment.push(r.message[x].name);
}
// show payment wizard
var dialog = new wn.ui.Dialog({
width: 400,
title: 'Payment',
fields: [
{fieldtype:'Data', fieldname:'total_amount', label:'Total Amount', read_only:1},
{fieldtype:'Select', fieldname:'mode_of_payment', label:'Mode of Payment',
options:mode_of_payment.join('\n'), reqd: 1},
{fieldtype:'Button', fieldname:'pay', label:'Pay'}
]
});
dialog.set_values({
"total_amount": $(".grand-total").text()
});
dialog.show();
dialog.get_input("total_amount").attr("disabled", "disabled");
dialog.fields_dict.pay.input.onclick = function() {
cur_frm.set_value("mode_of_payment", dialog.get_values().mode_of_payment);
cur_frm.set_value("paid_amount", dialog.get_values().total_amount);
cur_frm.save();
dialog.hide();
me.refresh();
};
}
}); });
} }
}) },
});

View File

@ -0,0 +1,32 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import webnotes
@webnotes.whitelist()
def get_items(price_list, item=None, item_group=None):
condition = ""
if item_group and item_group != "All Item Groups":
condition = "and i.item_group='%s'" % item_group
if item:
condition = "and i.name='%s'" % item
return webnotes.conn.sql("""select
i.name, i.item_name, i.image, ip.ref_rate, ip.ref_currency
from `tabItem` i LEFT JOIN `tabItem Price` ip
ON ip.parent=i.name
and ip.price_list=%s
where
i.is_sales_item='Yes'%s""" % ('%s', condition), (price_list), as_dict=1)
@webnotes.whitelist()
def get_item_from_barcode(barcode):
return webnotes.conn.sql("""select name from `tabItem` where barcode=%s""",
(barcode), as_dict=1)
@webnotes.whitelist()
def get_mode_of_payment():
return webnotes.conn.sql("""select name from `tabMode of Payment`""", as_dict=1)

View File

@ -0,0 +1,15 @@
.pos-item {
height: 200px;
overflow: hidden;
cursor: pointer;
padding-left: 5px !important;
padding-right: 5px !important;
}
.pos-bill {
padding: 20px 5px;
font-family: Monospace;
border: 1px solid #eee;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
}

View File

@ -12,7 +12,7 @@ cur_frm.pformat.print_heading = 'Invoice';
wn.require('app/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js'); wn.require('app/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js');
wn.require('app/utilities/doctype/sms_control/sms_control.js'); wn.require('app/utilities/doctype/sms_control/sms_control.js');
wn.require('app/selling/doctype/sales_common/sales_common.js'); wn.require('app/selling/doctype/sales_common/sales_common.js');
// wn.require('app/accounts/doctype/sales_invoice/pos.js'); wn.require('app/accounts/doctype/sales_invoice/pos.js');
wn.provide("erpnext.accounts"); wn.provide("erpnext.accounts");
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
@ -25,9 +25,8 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.frm.set_df_property("debit_to", "print_hide", 0); this.frm.set_df_property("debit_to", "print_hide", 0);
} }
} }
// if(this.frm.doc.is_pos && this.frm.doc.docstatus===0) {
// cur_frm.cscript.toggle_pos(true); cur_frm.cscript.toggle_pos(true);
// }
}, },
refresh: function(doc, dt, dn) { refresh: function(doc, dt, dn) {
@ -58,7 +57,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
cur_frm.add_custom_button('Make Payment Entry', cur_frm.cscript.make_bank_voucher); cur_frm.add_custom_button('Make Payment Entry', cur_frm.cscript.make_bank_voucher);
} }
if (this.frm.doc.docstatus===0) { if (doc.docstatus===0) {
cur_frm.add_custom_button(wn._('From Sales Order'), cur_frm.add_custom_button(wn._('From Sales Order'),
function() { function() {
wn.model.map_current_doc({ wn.model.map_current_doc({
@ -93,14 +92,43 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
}); });
}); });
// cur_frm.add_custom_button(wn._("POS View"), function() { if(cint(sys_defaults.fs_pos_view)===1)
// cur_frm.cscript.toggle_pos(); cur_frm.cscript.pos_btn();
// }, 'icon-desktop');
// setTimeout(function() { cur_frm.$pos_btn.click(); }, 1000);
} else {
// hide shown pos for submitted records
if(cur_frm.pos_active) cur_frm.cscript.toggle_pos(false);
} }
}, },
pos_btn: function() {
if(cur_frm.$pos_btn)
cur_frm.$pos_btn.remove();
if(!cur_frm.pos_active) {
var btn_label = wn._("POS View"),
icon = "icon-desktop";
} else {
var btn_label = wn._("Invoice View"),
icon = "icon-file-text";
}
cur_frm.$pos_btn = cur_frm.add_custom_button(btn_label, function() {
cur_frm.cscript.toggle_pos();
cur_frm.cscript.pos_btn();
}, icon);
},
toggle_pos: function(show) { toggle_pos: function(show) {
if(cint(sys_defaults.fs_pos_view)===0) return;
if(!(this.frm.doc.is_pos && this.frm.doc.docstatus===0)) return;
if (!this.frm.doc.selling_price_list)
msgprint(wn._("Please select Price List"))
else {
if((show===true && cur_frm.pos_active) || (show===false && !cur_frm.pos_active)) return; if((show===true && cur_frm.pos_active) || (show===false && !cur_frm.pos_active)) return;
// make pos // make pos
@ -116,8 +144,9 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
// refresh // refresh
if(cur_frm.pos_active) if(cur_frm.pos_active)
cur_frm.pos.refresh(); cur_frm.pos.refresh();
}
}, },
tc_name: function() { tc_name: function() {
this.get_terms(); this.get_terms();
}, },
@ -181,7 +210,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
set_dynamic_labels: function() { set_dynamic_labels: function() {
this._super(); this._super();
this.hide_fields(this.frm.doc); this.hide_fields(this.frm.doc);
},
entries_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no(grid_row)
} }
}); });
// for backward compatibility: combine new and previous states // for backward compatibility: combine new and previous states
@ -250,13 +284,13 @@ cur_frm.cscript['Make Delivery Note'] = function() {
cur_frm.cscript.make_bank_voucher = function() { cur_frm.cscript.make_bank_voucher = function() {
return wn.call({ return wn.call({
method: "accounts.doctype.journal_voucher.journal_voucher.get_default_bank_cash_account", method: "accounts.doctype.journal_voucher.journal_voucher.get_payment_entry_from_sales_invoice",
args: { args: {
"company": cur_frm.doc.company, "sales_invoice": cur_frm.doc.name
"voucher_type": "Bank Voucher"
}, },
callback: function(r) { callback: function(r) {
cur_frm.cscript.make_jv(cur_frm.doc, null, null, r.message); var doclist = wn.model.sync(r.message);
wn.set_route("Form", doclist[0].doctype, doclist[0].name);
} }
}); });
} }
@ -390,34 +424,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn){
refresh_field(cur_frm.cscript.fname); refresh_field(cur_frm.cscript.fname);
} }
// Make Journal Voucher
// --------------------
cur_frm.cscript.make_jv = function(doc, dt, dn, bank_account) {
var jv = wn.model.make_new_doc_and_get_name('Journal Voucher');
jv = locals['Journal Voucher'][jv];
jv.voucher_type = 'Bank Voucher';
jv.company = doc.company;
jv.remark = repl('Payment received against invoice %(vn)s for %(rem)s', {vn:doc.name, rem:doc.remarks});
jv.fiscal_year = doc.fiscal_year;
// debit to creditor
var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries');
d1.account = doc.debit_to;
d1.credit = doc.outstanding_amount;
d1.against_invoice = doc.name;
// credit to bank
var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries');
d1.account = bank_account.account;
d1.debit = doc.outstanding_amount;
d1.balance = bank_account.balance;
loaddoc('Journal Voucher', jv.name);
}
cur_frm.cscript.on_submit = function(doc, cdt, cdn) { cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
if(cint(wn.boot.notification_settings.sales_invoice)) { if(cint(wn.boot.notification_settings.sales_invoice)) {
cur_frm.email_doc(wn.boot.notification_settings.sales_invoice_message); cur_frm.email_doc(wn.boot.notification_settings.sales_invoice_message);

View File

@ -65,9 +65,6 @@ class DocType(SellingController):
self.validate_write_off_account() self.validate_write_off_account()
if cint(self.doc.update_stock): if cint(self.doc.update_stock):
sl = get_obj('Stock Ledger')
sl.validate_serial_no(self, 'entries')
sl.validate_serial_no(self, 'packing_details')
self.validate_item_code() self.validate_item_code()
self.update_current_stock() self.update_current_stock()
self.validate_delivery_note() self.validate_delivery_note()
@ -85,14 +82,8 @@ class DocType(SellingController):
def on_submit(self): def on_submit(self):
if cint(self.doc.update_stock) == 1: if cint(self.doc.update_stock) == 1:
sl_obj = get_obj("Stock Ledger")
sl_obj.validate_serial_no_warehouse(self, 'entries')
sl_obj.validate_serial_no_warehouse(self, 'packing_details')
sl_obj.update_serial_record(self, 'entries', is_submit = 1, is_incoming = 0)
sl_obj.update_serial_record(self, 'packing_details', is_submit = 1, is_incoming = 0)
self.update_stock_ledger(update_stock=1) self.update_stock_ledger(update_stock=1)
self.update_serial_nos()
else: else:
# Check for Approving Authority # Check for Approving Authority
if not self.doc.recurring_id: if not self.doc.recurring_id:
@ -120,11 +111,8 @@ class DocType(SellingController):
def on_cancel(self): def on_cancel(self):
if cint(self.doc.update_stock) == 1: if cint(self.doc.update_stock) == 1:
sl = get_obj('Stock Ledger')
sl.update_serial_record(self, 'entries', is_submit = 0, is_incoming = 0)
sl.update_serial_record(self, 'packing_details', is_submit = 0, is_incoming = 0)
self.update_stock_ledger(update_stock = -1) self.update_stock_ledger(update_stock = -1)
self.update_serial_nos(cancel = True)
sales_com_obj = get_obj(dt = 'Sales Common') sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_stop_sales_order(self) sales_com_obj.check_stop_sales_order(self)
@ -162,12 +150,22 @@ class DocType(SellingController):
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
self.set_pos_fields(for_validate) self.set_pos_fields(for_validate)
if not self.doc.debit_to:
self.doc.debit_to = self.get_customer_account()
if not self.doc.due_date:
self.doc.due_date = self.get_due_date()
super(DocType, self).set_missing_values(for_validate) super(DocType, self).set_missing_values(for_validate)
def set_customer_defaults(self): def set_customer_defaults(self):
# TODO cleanup these methods # TODO cleanup these methods
self.doc.fields.update(self.get_debit_to()) if self.doc.customer:
self.get_cust_and_due_date() self.doc.debit_to = self.get_customer_account()
elif self.doc.debit_to:
self.doc.customer = webnotes.conn.get_value('Account', self.doc.debit_to, 'master_name')
self.doc.due_date = self.get_due_date()
super(DocType, self).set_customer_defaults() super(DocType, self).set_customer_defaults()
@ -243,27 +241,24 @@ class DocType(SellingController):
You must first create it from the Customer Master" % You must first create it from the Customer Master" %
(self.doc.customer, self.doc.company)) (self.doc.customer, self.doc.company))
def get_debit_to(self): def get_due_date(self):
acc_head = self.get_customer_account()
return acc_head and {'debit_to' : acc_head} or {}
def get_cust_and_due_date(self):
"""Set Due Date = Posting Date + Credit Days""" """Set Due Date = Posting Date + Credit Days"""
due_date = None
if self.doc.posting_date: if self.doc.posting_date:
credit_days = 0 credit_days = 0
if self.doc.debit_to: if self.doc.debit_to:
credit_days = webnotes.conn.get_value("Account", self.doc.debit_to, "credit_days") credit_days = webnotes.conn.get_value("Account", self.doc.debit_to, "credit_days")
if self.doc.customer and not credit_days:
credit_days = webnotes.conn.get_value("Customer", self.doc.customer, "credit_days")
if self.doc.company and not credit_days: if self.doc.company and not credit_days:
credit_days = webnotes.conn.get_value("Company", self.doc.company, "credit_days") credit_days = webnotes.conn.get_value("Company", self.doc.company, "credit_days")
if credit_days: if credit_days:
self.doc.due_date = add_days(self.doc.posting_date, credit_days) due_date = add_days(self.doc.posting_date, credit_days)
else: else:
self.doc.due_date = self.doc.posting_date due_date = self.doc.posting_date
if self.doc.debit_to: return due_date
self.doc.customer = webnotes.conn.get_value('Account',self.doc.debit_to,'master_name')
def get_barcode_details(self, barcode): def get_barcode_details(self, barcode):
return get_obj('Sales Common').get_barcode_details(barcode) return get_obj('Sales Common').get_barcode_details(barcode)
@ -487,10 +482,6 @@ class DocType(SellingController):
def make_packing_list(self): def make_packing_list(self):
get_obj('Sales Common').make_packing_list(self,'entries') get_obj('Sales Common').make_packing_list(self,'entries')
sl = get_obj('Stock Ledger')
sl.scrub_serial_nos(self)
sl.scrub_serial_nos(self, 'packing_details')
def on_update(self): def on_update(self):
if cint(self.doc.update_stock) == 1: if cint(self.doc.update_stock) == 1:
@ -502,6 +493,11 @@ class DocType(SellingController):
if not d.warehouse: if not d.warehouse:
d.warehouse = cstr(w) d.warehouse = cstr(w)
self.make_packing_list()
else:
self.doclist = self.doc.clear_table(self.doclist, 'packing_details')
if cint(self.doc.is_pos) == 1:
if flt(self.doc.paid_amount) == 0: if flt(self.doc.paid_amount) == 0:
if self.doc.cash_bank_account: if self.doc.cash_bank_account:
webnotes.conn.set(self.doc, 'paid_amount', webnotes.conn.set(self.doc, 'paid_amount',
@ -510,10 +506,7 @@ class DocType(SellingController):
# show message that the amount is not paid # show message that the amount is not paid
webnotes.conn.set(self.doc,'paid_amount',0) webnotes.conn.set(self.doc,'paid_amount',0)
webnotes.msgprint("Note: Payment Entry will not be created since 'Cash/Bank Account' was not specified.") webnotes.msgprint("Note: Payment Entry will not be created since 'Cash/Bank Account' was not specified.")
self.make_packing_list()
else: else:
self.doclist = self.doc.clear_table(self.doclist, 'packing_details')
webnotes.conn.set(self.doc,'paid_amount',0) webnotes.conn.set(self.doc,'paid_amount',0)
def check_prev_docstatus(self): def check_prev_docstatus(self):
@ -556,9 +549,7 @@ class DocType(SellingController):
self.values = [] self.values = []
items = get_obj('Sales Common').get_item_list(self) items = get_obj('Sales Common').get_item_list(self)
for d in items: for d in items:
stock_item = webnotes.conn.sql("SELECT is_stock_item, is_sample_item \ if webnotes.conn.get_value("Item", d['item_code'], "is_stock_item") == "Yes":
FROM tabItem where name = '%s'"%(d['item_code']), as_dict = 1)
if stock_item[0]['is_stock_item'] == "Yes":
if not d['warehouse']: if not d['warehouse']:
msgprint("Message: Please enter Warehouse for item %s as it is stock item." \ msgprint("Message: Please enter Warehouse for item %s as it is stock item." \
% d['item_code'], raise_exception=1) % d['item_code'], raise_exception=1)

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-05-24 19:29:05", "creation": "2013-05-24 19:29:05",
"docstatus": 0, "docstatus": 0,
"modified": "2013-08-09 14:45:42", "modified": "2013-08-31 10:19:01",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -179,7 +179,6 @@
"search_index": 1 "search_index": 1
}, },
{ {
"default": "Today",
"description": "Enter the date by which payments from customer is expected against this invoice.", "description": "Enter the date by which payments from customer is expected against this invoice.",
"doctype": "DocField", "doctype": "DocField",
"fieldname": "due_date", "fieldname": "due_date",

View File

@ -645,6 +645,60 @@ class TestSalesInvoice(unittest.TestCase):
for i in xrange(count): for i in xrange(count):
base_si = _test(i) base_si = _test(i)
def test_serialized(self):
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
si = webnotes.bean(copy=test_records[0])
si.doc.update_stock = 1
si.doclist[1].item_code = "_Test Serialized Item With Series"
si.doclist[1].qty = 1
si.doclist[1].serial_no = serial_nos[0]
si.insert()
si.submit()
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Delivered")
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0],
"delivery_document_no"), si.doc.name)
return si
def test_serialized_cancel(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
si = self.test_serialized()
si.cancel()
serial_nos = get_serial_nos(si.doclist[1].serial_no)
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Available")
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0],
"delivery_document_no"))
def test_serialize_status(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
sr = webnotes.bean("Serial No", serial_nos[0])
sr.doc.status = "Not Available"
sr.save()
si = webnotes.bean(copy=test_records[0])
si.doc.update_stock = 1
si.doclist[1].item_code = "_Test Serialized Item With Series"
si.doclist[1].qty = 1
si.doclist[1].serial_no = serial_nos[0]
si.insert()
self.assertRaises(SerialNoStatusError, si.submit)
test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"] test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"]
test_records = [ test_records = [
@ -916,7 +970,6 @@ test_records = [
"item_name": "_Test Item Home Desktop 100", "item_name": "_Test Item Home Desktop 100",
"qty": 10, "qty": 10,
"ref_rate": 62.5, "ref_rate": 62.5,
"export_rate": 62.5,
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"item_tax_rate": json.dumps({"_Test Account Excise Duty - _TC": 10}), "item_tax_rate": json.dumps({"_Test Account Excise Duty - _TC": 10}),
"income_account": "Sales - _TC", "income_account": "Sales - _TC",
@ -930,7 +983,6 @@ test_records = [
"item_name": "_Test Item Home Desktop 200", "item_name": "_Test Item Home Desktop 200",
"qty": 5, "qty": 5,
"ref_rate": 190.66, "ref_rate": 190.66,
"export_rate": 190.66,
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"income_account": "Sales - _TC", "income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-01-27 16:30:52", "creation": "2013-01-27 16:30:52",
"docstatus": 0, "docstatus": 0,
"modified": "2013-07-11 14:41:59", "modified": "2013-08-14 12:47:45",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -19,14 +19,18 @@
"name": "__common__", "name": "__common__",
"parent": "Financial Statements", "parent": "Financial Statements",
"parentfield": "roles", "parentfield": "roles",
"parenttype": "Page", "parenttype": "Page"
"role": "Accounts Manager"
}, },
{ {
"doctype": "Page", "doctype": "Page",
"name": "Financial Statements" "name": "Financial Statements"
}, },
{ {
"doctype": "Page Role" "doctype": "Page Role",
"role": "Accounts Manager"
},
{
"doctype": "Page Role",
"role": "Analytics"
} }
] ]

View File

@ -141,13 +141,13 @@ erpnext.GeneralLedger = wn.views.GridReport.extend({
toggle_group_by_checks: function() { toggle_group_by_checks: function() {
this.make_account_by_name(); this.make_account_by_name();
this.filter_inputs.group_by_ledger // this.filter_inputs.group_by_ledger
.parent().toggle(!!(this.account_by_name[this.account] // .parent().toggle(!!(this.account_by_name[this.account]
&& this.account_by_name[this.account].group_or_ledger==="Group")); // && this.account_by_name[this.account].group_or_ledger==="Group"));
//
this.filter_inputs.group_by_voucher // this.filter_inputs.group_by_voucher
.parent().toggle(!!(this.account_by_name[this.account] // .parent().toggle(!!(this.account_by_name[this.account]
&& this.account_by_name[this.account].group_or_ledger==="Ledger")); // && this.account_by_name[this.account].group_or_ledger==="Ledger"));
}, },
prepare_data: function() { prepare_data: function() {
var me = this; var me = this;
@ -389,7 +389,8 @@ erpnext.GeneralLedger = wn.views.GridReport.extend({
grid: { hoverable: true, clickable: true }, grid: { hoverable: true, clickable: true },
xaxis: { mode: "time", xaxis: { mode: "time",
min: dateutil.str_to_obj(this.from_date).getTime(), min: dateutil.str_to_obj(this.from_date).getTime(),
max: dateutil.str_to_obj(this.to_date).getTime() } max: dateutil.str_to_obj(this.to_date).getTime() },
series: { downsample: { threshold: 1000 } }
} }
}, },
}); });

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes import webnotes
from webnotes.utils import flt from webnotes.utils import flt
from stock.utils import get_buying_amount from stock.utils import get_buying_amount, get_sales_bom_buying_amount
def execute(filters=None): def execute(filters=None):
if not filters: filters = {} if not filters: filters = {}
@ -22,9 +22,14 @@ def execute(filters=None):
for row in source: for row in source:
selling_amount = flt(row.amount) selling_amount = flt(row.amount)
buying_amount = get_buying_amount(row.item_code, row.parenttype, row.name, row.item_row, item_sales_bom_map = item_sales_bom.get(row.parenttype, {}).get(row.name, webnotes._dict())
stock_ledger_entries.get((row.item_code, row.warehouse), []),
item_sales_bom.get(row.parenttype, {}).get(row.name, webnotes._dict())) if item_sales_bom_map.get(row.item_code):
buying_amount = get_sales_bom_buying_amount(row.item_code, row.warehouse,
row.parenttype, row.name, row.item_row, stock_ledger_entries, item_sales_bom_map)
else:
buying_amount = get_buying_amount(row.parenttype, row.name, row.item_row,
stock_ledger_entries.get((row.item_code, row.warehouse), []))
buying_amount = buying_amount > 0 and buying_amount or 0 buying_amount = buying_amount > 0 and buying_amount or 0

View File

@ -20,8 +20,7 @@ class DocType(BuyingController):
def is_item_table_empty(self, obj): def is_item_table_empty(self, obj):
if not len(obj.doclist.get({"parentfield": obj.fname})): if not len(obj.doclist.get({"parentfield": obj.fname})):
msgprint(_("Hey there! You need to put at least one item in \ msgprint(_("You need to put at least one item in the item table."), raise_exception=True)
the item table."), raise_exception=True)
def get_supplier_details(self, name = ''): def get_supplier_details(self, name = ''):
details = sql("select supplier_name,address from `tabSupplier` where name = '%s' and docstatus != 2" %(name), as_dict = 1) details = sql("select supplier_name,address from `tabSupplier` where name = '%s' and docstatus != 2" %(name), as_dict = 1)

View File

@ -46,10 +46,10 @@ cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
if (doc.item_code) { if (doc.item_code) {
filter = { filter = {
'item_code': doc.item_code, 'item_code': doc.item_code,
'status': "In Store" 'status': "Available"
} }
} else } else
filter = { 'status': "In Store" } filter = { 'status': "Available" }
return { filters: filter } return { filters: filter }
} }

View File

@ -110,6 +110,11 @@ wn.module_page["Buying"] = [
right: true, right: true,
icon: "icon-list", icon: "icon-list",
items: [ items: [
{
"label":wn._("Items To Be Requested"),
route: "query-report/Items To Be Requested",
doctype: "Item"
},
{ {
"label":wn._("Requested Items To Be Ordered"), "label":wn._("Requested Items To Be Ordered"),
route: "query-report/Requested Items To Be Ordered", route: "query-report/Requested Items To Be Ordered",

View File

@ -1,4 +1,5 @@
{ {
"app_name": "ERPNext",
"modules": { "modules": {
"Selling": { "Selling": {
"link": "selling-home", "link": "selling-home",
@ -129,7 +130,7 @@
}, },
"writers": { "writers": {
"template": "app/website/templates/pages/writers", "template": "app/website/templates/pages/writers",
"args_method": "website.helpers.blog.get_writers_args" "args_method": "website.doctype.blogger.blogger.get_writers_args"
}, },
"profile": { "profile": {
"no_cache": true, "no_cache": true,

View File

@ -75,7 +75,7 @@ class AccountsController(TransactionBase):
self.doc.conversion_rate = self.doc.plc_conversion_rate self.doc.conversion_rate = self.doc.plc_conversion_rate
if self.meta.get_field("currency"): if self.meta.get_field("currency"):
if self.doc.currency != company_currency: if self.doc.currency and self.doc.currency != company_currency:
if not self.doc.conversion_rate: if not self.doc.conversion_rate:
exchange = self.doc.currency + "-" + company_currency exchange = self.doc.currency + "-" + company_currency
self.doc.conversion_rate = flt(webnotes.conn.get_value("Currency Exchange", self.doc.conversion_rate = flt(webnotes.conn.get_value("Currency Exchange",

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes import webnotes
from webnotes import _, msgprint from webnotes import _, msgprint
from webnotes.utils import flt from webnotes.utils import flt, _round
from buying.utils import get_item_details from buying.utils import get_item_details
from setup.utils import get_company_currency from setup.utils import get_company_currency
@ -17,7 +17,6 @@ class BuyingController(StockController):
def onload_post_render(self): def onload_post_render(self):
# contact, address, item details # contact, address, item details
self.set_missing_values() self.set_missing_values()
self.set_taxes("purchase_tax_details", "purchase_other_charges")
def validate(self): def validate(self):
super(BuyingController, self).validate() super(BuyingController, self).validate()
@ -40,6 +39,8 @@ class BuyingController(StockController):
self.doc.fields[fieldname] = val self.doc.fields[fieldname] = val
self.set_missing_item_details(get_item_details) self.set_missing_item_details(get_item_details)
if self.doc.fields.get("__islocal"):
self.set_taxes("purchase_tax_details", "purchase_other_charges")
def set_supplier_from_item_default(self): def set_supplier_from_item_default(self):
if self.meta.get_field("supplier") and not self.doc.supplier: if self.meta.get_field("supplier") and not self.doc.supplier:
@ -98,7 +99,7 @@ class BuyingController(StockController):
if item.discount_rate == 100.0: if item.discount_rate == 100.0:
item.import_rate = 0.0 item.import_rate = 0.0
elif item.import_ref_rate: elif not item.import_rate:
item.import_rate = flt(item.import_ref_rate * (1.0 - (item.discount_rate / 100.0)), item.import_rate = flt(item.import_ref_rate * (1.0 - (item.discount_rate / 100.0)),
self.precision("import_rate", item)) self.precision("import_rate", item))
@ -129,10 +130,10 @@ class BuyingController(StockController):
self.precision("total_tax")) self.precision("total_tax"))
if self.meta.get_field("rounded_total"): if self.meta.get_field("rounded_total"):
self.doc.rounded_total = round(self.doc.grand_total) self.doc.rounded_total = _round(self.doc.grand_total)
if self.meta.get_field("rounded_total_import"): if self.meta.get_field("rounded_total_import"):
self.doc.rounded_total_import = round(self.doc.grand_total_import) self.doc.rounded_total_import = _round(self.doc.grand_total_import)
def calculate_outstanding_amount(self): def calculate_outstanding_amount(self):
if self.doc.doctype == "Purchase Invoice" and self.doc.docstatus < 2: if self.doc.doctype == "Purchase Invoice" and self.doc.docstatus < 2:

View File

@ -16,11 +16,14 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) {
if(doc.__islocal) { if(doc.__islocal) {
var last_route = wn.route_history.slice(-2, -1)[0]; var last_route = wn.route_history.slice(-2, -1)[0];
if(last_route && last_route[0]==="Form") { if(last_route && last_route[0]==="Form") {
var doctype = last_route[1],
docname = last_route.slice(2).join("/");
if(["Customer", "Quotation", "Sales Order", "Sales Invoice", "Delivery Note", if(["Customer", "Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
"Installation Note", "Opportunity", "Customer Issue", "Maintenance Visit", "Installation Note", "Opportunity", "Customer Issue", "Maintenance Visit",
"Maintenance Schedule"] "Maintenance Schedule"]
.indexOf(last_route[1])!==-1) { .indexOf(doctype)!==-1) {
var refdoc = wn.model.get_doc(last_route[1], last_route[2]); var refdoc = wn.model.get_doc(doctype, docname);
if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Customer" : true) { if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Customer" : true) {
cur_frm.set_value("customer", refdoc.customer || refdoc.name); cur_frm.set_value("customer", refdoc.customer || refdoc.name);
@ -30,16 +33,16 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) {
} }
} }
if(["Supplier", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"] if(["Supplier", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"]
.indexOf(last_route[1])!==-1) { .indexOf(doctype)!==-1) {
var refdoc = wn.model.get_doc(last_route[1], last_route[2]); var refdoc = wn.model.get_doc(doctype, docname);
cur_frm.set_value("supplier", refdoc.supplier || refdoc.name); cur_frm.set_value("supplier", refdoc.supplier || refdoc.name);
cur_frm.set_value("supplier_name", refdoc.supplier_name); cur_frm.set_value("supplier_name", refdoc.supplier_name);
if(cur_frm.doc.doctype==="Address") if(cur_frm.doc.doctype==="Address")
cur_frm.set_value("address_title", cur_frm.doc.supplier_name); cur_frm.set_value("address_title", cur_frm.doc.supplier_name);
} }
if(["Lead", "Quotation"] if(["Lead", "Quotation"]
.indexOf(last_route[1])!==-1) { .indexOf(doctype)!==-1) {
var refdoc = wn.model.get_doc(last_route[1], last_route[2]); var refdoc = wn.model.get_doc(doctype, docname);
if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Lead" : true) { if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Lead" : true) {
cur_frm.set_value("lead", refdoc.lead || refdoc.name); cur_frm.set_value("lead", refdoc.lead || refdoc.name);

View File

@ -207,4 +207,4 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
"fcond": get_filters_cond(doctype, filters, []), "fcond": get_filters_cond(doctype, filters, []),
"mcond": get_match_cond(doctype), "mcond": get_match_cond(doctype),
"start": "%(start)s", "page_len": "%(page_len)s", "txt": "%(txt)s" "start": "%(start)s", "page_len": "%(page_len)s", "txt": "%(txt)s"
}, { "start": start, "page_len": page_len, "txt": ("%%%s%%" % txt) }, debug=True) }, { "start": start, "page_len": page_len, "txt": ("%%%s%%" % txt) })

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes import webnotes
from webnotes.utils import cint, flt, comma_or from webnotes.utils import cint, flt, comma_or, _round, add_days, cstr
from setup.utils import get_company_currency from setup.utils import get_company_currency
from selling.utils import get_item_details from selling.utils import get_item_details
from webnotes import msgprint, _ from webnotes import msgprint, _
@ -15,18 +15,14 @@ class SellingController(StockController):
# contact, address, item details and pos details (if applicable) # contact, address, item details and pos details (if applicable)
self.set_missing_values() self.set_missing_values()
self.set_taxes("other_charges", "charge")
if self.meta.get_field("debit_to") and not self.doc.debit_to:
self.doc.debit_to = self.get_debit_to().get("debit_to")
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
super(SellingController, self).set_missing_values(for_validate) super(SellingController, self).set_missing_values(for_validate)
# set contact and address details for customer, if they are not mentioned # set contact and address details for customer, if they are not mentioned
self.set_missing_lead_customer_details() self.set_missing_lead_customer_details()
self.set_price_list_and_item_details() self.set_price_list_and_item_details()
if self.doc.fields.get("__islocal"):
self.set_taxes("other_charges", "charge")
def set_missing_lead_customer_details(self): def set_missing_lead_customer_details(self):
if self.doc.customer: if self.doc.customer:
@ -89,7 +85,7 @@ class SellingController(StockController):
self.doc.grand_total_export or self.doc.rounded_total_export, self.doc.currency) self.doc.grand_total_export or self.doc.rounded_total_export, self.doc.currency)
def set_buying_amount(self, stock_ledger_entries = None): def set_buying_amount(self, stock_ledger_entries = None):
from stock.utils import get_buying_amount from stock.utils import get_buying_amount, get_sales_bom_buying_amount
if not stock_ledger_entries: if not stock_ledger_entries:
stock_ledger_entries = self.get_stock_ledger_entries() stock_ledger_entries = self.get_stock_ledger_entries()
@ -103,8 +99,13 @@ class SellingController(StockController):
for item in self.doclist.get({"parentfield": self.fname}): for item in self.doclist.get({"parentfield": self.fname}):
if item.item_code in self.stock_items or \ if item.item_code in self.stock_items or \
(item_sales_bom and item_sales_bom.get(item.item_code)): (item_sales_bom and item_sales_bom.get(item.item_code)):
buying_amount = get_buying_amount(item.item_code, self.doc.doctype, self.doc.name, item.name, if item.item_code in self.stock_items:
stock_ledger_entries.get((item.item_code, item.warehouse), []), buying_amount = get_buying_amount(self.doc.doctype, self.doc.name,
item.name, stock_ledger_entries.get((item.item_code,
item.warehouse), []))
elif item_sales_bom and item_sales_bom.get(item.item_code):
buying_amount = get_sales_bom_buying_amount(item.item_code, item.warehouse,
self.doc.doctype, self.doc.name, item.name, stock_ledger_entries,
item_sales_bom) item_sales_bom)
item.buying_amount = buying_amount >= 0.01 and buying_amount or 0 item.buying_amount = buying_amount >= 0.01 and buying_amount or 0
@ -191,7 +192,7 @@ class SellingController(StockController):
if item.adj_rate == 100: if item.adj_rate == 100:
item.export_rate = 0 item.export_rate = 0
elif item.ref_rate: elif not item.export_rate:
item.export_rate = flt(item.ref_rate * (1.0 - (item.adj_rate / 100.0)), item.export_rate = flt(item.ref_rate * (1.0 - (item.adj_rate / 100.0)),
self.precision("export_rate", item)) self.precision("export_rate", item))
@ -222,8 +223,8 @@ class SellingController(StockController):
self.doc.other_charges_total_export = flt(self.doc.grand_total_export - self.doc.net_total_export, self.doc.other_charges_total_export = flt(self.doc.grand_total_export - self.doc.net_total_export,
self.precision("other_charges_total_export")) self.precision("other_charges_total_export"))
self.doc.rounded_total = round(self.doc.grand_total) self.doc.rounded_total = _round(self.doc.grand_total)
self.doc.rounded_total_export = round(self.doc.grand_total_export) self.doc.rounded_total_export = _round(self.doc.grand_total_export)
def calculate_outstanding_amount(self): def calculate_outstanding_amount(self):
# NOTE: # NOTE:
@ -271,3 +272,32 @@ class SellingController(StockController):
msgprint(_(self.meta.get_label("order_type")) + " " + msgprint(_(self.meta.get_label("order_type")) + " " +
_("must be one of") + ": " + comma_or(valid_types), _("must be one of") + ": " + comma_or(valid_types),
raise_exception=True) raise_exception=True)
def update_serial_nos(self, cancel=False):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
update_serial_nos_after_submit(self, self.doc.doctype, self.fname)
update_serial_nos_after_submit(self, self.doc.doctype, "packing_details")
for table_fieldname in (self.fname, "packing_details"):
for d in self.doclist.get({"parentfield": table_fieldname}):
for serial_no in get_serial_nos(d.serial_no):
sr = webnotes.bean("Serial No", serial_no)
if cancel:
sr.doc.status = "Available"
for fieldname in ("warranty_expiry_date", "delivery_document_type",
"delivery_document_no", "delivery_date", "delivery_time", "customer",
"customer_name"):
sr.doc.fields[fieldname] = None
else:
sr.doc.delivery_document_type = self.doc.doctype
sr.doc.delivery_document_no = self.doc.name
sr.doc.delivery_date = self.doc.posting_date
sr.doc.delivery_time = self.doc.posting_time
sr.doc.customer = self.doc.customer
sr.doc.customer_name = self.doc.customer_name
if sr.doc.warranty_period:
sr.doc.warranty_expiry_date = add_days(cstr(self.doc.posting_date),
cint(sr.doc.warranty_period))
sr.doc.status = 'Delivered'
sr.save()

View File

@ -1,14 +0,0 @@
---
{
"_label": "Get Involved"
}
---
If you are an ERPNext user:
[https://groups.google.com/group/erpnext-user-forum](https://groups.google.com/group/erpnext-user-forum)
If you are an ERPNext developer:
[https://groups.google.com/group/erpnext-developer-forum](https://groups.google.com/group/erpnext-developer-forum)

View File

@ -7,28 +7,25 @@
"docs.dev", "docs.dev",
"docs.download", "docs.download",
"docs.community", "docs.community",
"docs.blog", "docs.blog"
"docs.about"
], ],
"_no_toc": 1 "_no_toc": 1
} }
--- ---
<div style="margin: 10px 0px"> <div class="text-center" style="margin: 10px 0px">
<h1 style="text-align: center">All-in-One Platform to Manage Your Organization.</h1> <h1>ERPNext Docs (beta)</h1>
<h3 style="text-align: center; font-weight: normal; color: #888">100% Free and Open Source.</h1> <h3 class="text-muted">Open Source ERP Built for The Web.</h3>
<p>For the main site, go to <a href="https://erpnext.com/">https://erpnext.com</a></p>
</div> </div>
![Home Screen](img/home.png) ![Home Screen](img/home.png)
Welcome to the ERPNext Documentation + Community Site
### What is ERPNext? ### What is ERPNext?
ERPNext is an information system that links together an entire organization's operations. It is a software package that offers convenience of managing all the business functions from a single platform. No need of going to different applications to process different requests. No need of saving data in different functional packages. Under one ERP "roof" you can manage Accounting, Warehouse Management, CRM, Human Resources, Supply Chain Management, Sales Management, and Website Design. ERPNext is an Open Source integrated app (that manages Financial Accounting, Inventory, CRM) that is built grounds up for the web, using some of the latest web technologies and frameworks. ERPNext helps your organization manage Financial Accounting, Inventory, Sales, Purchase, Payroll, Customer Support, E-Commerce all in one platform. Learn more at [https://erpnext.com](https://erpnext.com)
ERPNext is written by Web Notes Technologies keeping small and medium businesses in mind. ### Site Contents
- It gives better access to crucial information as a whole rather than in fragments of different versions. This site contains the full User and Developer Documentation for ERPNext. This is still a work-in-progress. Please feel free to contribute issues and documentation.
- It provides comparable financial reports.
- It avoids duplication of reports and redundant data.
- It allows better alignment across cross-functional departments.
- It facilitates Website Design and provides shopping cart facility.
- It gives better deployment on mobiles, tablets, desktops and large screens.

View File

@ -156,20 +156,23 @@ class DocType:
raise_exception=InvalidLeaveApproverError) raise_exception=InvalidLeaveApproverError)
def update_dob_event(self): def update_dob_event(self):
if self.doc.date_of_birth: if self.doc.status == "Active" and self.doc.date_of_birth:
get_events = webnotes.conn.sql("""select name from `tabEvent` where repeat_on='Every Year' birthday_event = webnotes.conn.sql("""select name from `tabEvent` where repeat_on='Every Year'
and ref_type='Employee' and ref_name=%s""", self.doc.name) and ref_type='Employee' and ref_name=%s""", self.doc.name)
starts_on = self.doc.date_of_birth + " 00:00:00" starts_on = self.doc.date_of_birth + " 00:00:00"
ends_on = self.doc.date_of_birth + " 00:15:00" ends_on = self.doc.date_of_birth + " 00:15:00"
if get_events: if birthday_event:
webnotes.conn.sql("""update `tabEvent` set starts_on=%s, ends_on=%s event = webnotes.bean("Event", birthday_event[0][0])
where name=%s""", (starts_on, ends_on, get_events[0][0])) event.doc.starts_on = starts_on
event.doc.ends_on = ends_on
event.save()
else: else:
webnotes.bean({ webnotes.bean({
"doctype": "Event", "doctype": "Event",
"subject": _("Birthday") + ": " + self.doc.employee_name, "subject": _("Birthday") + ": " + self.doc.employee_name,
"description": _("Happy Birthday!") + " " + self.doc.employee_name,
"starts_on": starts_on, "starts_on": starts_on,
"ends_on": ends_on, "ends_on": ends_on,
"event_type": "Public", "event_type": "Public",

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes import webnotes
from webnotes.utils import add_days, cint, cstr, flt, getdate, nowdate from webnotes.utils import add_days, cint, cstr, flt, getdate, nowdate, _round
from webnotes.model.doc import make_autoname from webnotes.model.doc import make_autoname
from webnotes.model.bean import getlist from webnotes.model.bean import getlist
from webnotes.model.code import get_obj from webnotes.model.code import get_obj
@ -164,7 +164,7 @@ class DocType(TransactionBase):
self.doc.gross_pay = flt(self.doc.arrear_amount) + flt(self.doc.leave_encashment_amount) self.doc.gross_pay = flt(self.doc.arrear_amount) + flt(self.doc.leave_encashment_amount)
for d in self.doclist.get({"parentfield": "earning_details"}): for d in self.doclist.get({"parentfield": "earning_details"}):
if cint(d.e_depends_on_lwp) == 1: if cint(d.e_depends_on_lwp) == 1:
d.e_modified_amount = round(flt(d.e_amount) * flt(self.doc.payment_days) d.e_modified_amount = _round(flt(d.e_amount) * flt(self.doc.payment_days)
/ cint(self.doc.total_days_in_month), 2) / cint(self.doc.total_days_in_month), 2)
elif not self.doc.payment_days: elif not self.doc.payment_days:
d.e_modified_amount = 0 d.e_modified_amount = 0
@ -176,7 +176,7 @@ class DocType(TransactionBase):
self.doc.total_deduction = 0 self.doc.total_deduction = 0
for d in getlist(self.doclist, 'deduction_details'): for d in getlist(self.doclist, 'deduction_details'):
if cint(d.d_depends_on_lwp) == 1: if cint(d.d_depends_on_lwp) == 1:
d.d_modified_amount = round(flt(d.d_amount) * flt(self.doc.payment_days) d.d_modified_amount = _round(flt(d.d_amount) * flt(self.doc.payment_days)
/ cint(self.doc.total_days_in_month), 2) / cint(self.doc.total_days_in_month), 2)
elif not self.doc.payment_days: elif not self.doc.payment_days:
d.d_modified_amount = 0 d.d_modified_amount = 0
@ -189,7 +189,7 @@ class DocType(TransactionBase):
self.calculate_earning_total() self.calculate_earning_total()
self.calculate_ded_total() self.calculate_ded_total()
self.doc.net_pay = flt(self.doc.gross_pay) - flt(self.doc.total_deduction) self.doc.net_pay = flt(self.doc.gross_pay) - flt(self.doc.total_deduction)
self.doc.rounded_total = round(self.doc.net_pay) self.doc.rounded_total = _round(self.doc.net_pay)
def on_submit(self): def on_submit(self):
if(self.doc.email_check == 1): if(self.doc.email_check == 1):

View File

@ -51,7 +51,7 @@ def validate_install():
distribution = platform.linux_distribution()[0].lower().replace('"', '') distribution = platform.linux_distribution()[0].lower().replace('"', '')
print "Distribution = ", distribution print "Distribution = ", distribution
is_redhat = distribution in ("redhat", "centos", "centos linux", "fedora") is_redhat = distribution in ("redhat", "centos", "centos linux", "fedora")
is_debian = distribution in ("debian", "ubuntu", "elementary os") is_debian = distribution in ("debian", "ubuntu", "elementary os", "linuxmint")
if not (is_redhat or is_debian): if not (is_redhat or is_debian):
raise Exception, "Sorry! This installer works only with yum or apt-get package management" raise Exception, "Sorry! This installer works only with yum or apt-get package management"
@ -59,7 +59,7 @@ def validate_install():
return is_redhat, is_debian return is_redhat, is_debian
def install_using_yum(): def install_using_yum():
packages = "python python-setuptools MySQL-python httpd git memcached ntp vim-enhanced screen" packages = "python python-setuptools gcc python-devel MySQL-python httpd git memcached ntp vim-enhanced screen"
print "-"*80 print "-"*80
print "Installing Packages: (This may take some time)" print "Installing Packages: (This may take some time)"
@ -108,7 +108,7 @@ def update_config_for_redhat():
def install_using_apt(): def install_using_apt():
exec_in_shell("apt-get update") exec_in_shell("apt-get update")
packages = "python python-setuptools python-mysqldb apache2 git memcached ntp vim screen htop" packages = "python python-setuptools python-dev build-essential python-pip python-mysqldb apache2 git memcached ntp vim screen htop"
print "-"*80 print "-"*80
print "Installing Packages: (This may take some time)" print "Installing Packages: (This may take some time)"
print packages print packages
@ -145,7 +145,11 @@ def install_python_modules():
print python_modules print python_modules
print "-"*80 print "-"*80
if not exec_in_shell("which pip"):
exec_in_shell("easy_install pip") exec_in_shell("easy_install pip")
exec_in_shell("pip install --upgrade pip")
exec_in_shell("pip install --upgrade virtualenv")
exec_in_shell("pip install -q %s" % python_modules) exec_in_shell("pip install -q %s" % python_modules)
def install_erpnext(install_path): def install_erpnext(install_path):

View File

@ -143,11 +143,18 @@ def get_item_details(item):
def make_stock_entry(production_order_id, purpose): def make_stock_entry(production_order_id, purpose):
production_order = webnotes.bean("Production Order", production_order_id) production_order = webnotes.bean("Production Order", production_order_id)
# validate already existing
ste = webnotes.conn.get_value("Stock Entry", {
"production_order":production_order_id,
"purpose": purpose
}, "name")
stock_entry = webnotes.new_bean("Stock Entry") stock_entry = webnotes.new_bean("Stock Entry")
stock_entry.doc.purpose = purpose stock_entry.doc.purpose = purpose
stock_entry.doc.production_order = production_order_id stock_entry.doc.production_order = production_order_id
stock_entry.doc.company = production_order.doc.company stock_entry.doc.company = production_order.doc.company
stock_entry.doc.bom_no = production_order.doc.bom_no stock_entry.doc.bom_no = production_order.doc.bom_no
stock_entry.doc.use_multi_level_bom = production_order.doc.use_multi_level_bom
stock_entry.doc.fg_completed_qty = flt(production_order.doc.qty) - flt(production_order.doc.produced_qty) stock_entry.doc.fg_completed_qty = flt(production_order.doc.qty) - flt(production_order.doc.produced_qty)
if purpose=="Material Transfer": if purpose=="Material Transfer":
@ -156,4 +163,5 @@ def make_stock_entry(production_order_id, purpose):
stock_entry.doc.from_warehouse = production_order.doc.wip_warehouse stock_entry.doc.from_warehouse = production_order.doc.wip_warehouse
stock_entry.doc.to_warehouse = production_order.doc.fg_warehouse stock_entry.doc.to_warehouse = production_order.doc.fg_warehouse
stock_entry.run_method("get_items")
return [d.fields for d in stock_entry.doclist] return [d.fields for d in stock_entry.doclist]

View File

@ -1,10 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
def execute():
import webnotes
from webnotes.modules import reload_doc
reload_doc('stock', 'doctype', 'serial_no')
webnotes.conn.sql("update `tabSerial No` set sle_exists = 1")

View File

@ -0,0 +1,49 @@
import webnotes
def execute():
create_fiscal_years()
doctypes = webnotes.conn.sql_list("""select parent from tabDocField
where (fieldtype="Link" and options='Fiscal Year')
or (fieldtype="Select" and options='link:Fiscal Year')""")
for dt in doctypes:
date_fields = webnotes.conn.sql_list("""select fieldname from tabDocField
where parent=%s and fieldtype='Date'""", dt)
date_field = get_date_field(date_fields, dt)
if not date_field:
print dt, date_field
else:
webnotes.conn.sql("""update `tab%s` set fiscal_year =
if(%s<='2013-06-30', '2012-2013', '2013-2014')""" % (dt, date_field))
def create_fiscal_years():
fiscal_years = {
"2012-2013": ["2012-07-01", "2013-06-30"],
"2013-2014": ["2013-07-01", "2014-06-30"]
}
for d in fiscal_years:
webnotes.bean({
"doctype": "Fiscal Year",
"year": d,
"year_start_date": fiscal_years[d][0],
"is_fiscal_year_closed": "No"
}).insert()
def get_date_field(date_fields, dt):
date_field = None
if date_fields:
if "posting_date" in date_fields:
date_field = "posting_date"
elif "transaction_date" in date_fields:
date_field = 'transaction_date'
else:
date_field = date_fields[0]
# print dt, date_fields
return date_field

View File

@ -5,6 +5,8 @@ from __future__ import unicode_literals
import webnotes import webnotes
def execute(): def execute():
webnotes.reload_doc("website", "doctype", "shopping_cart_price_list")
for t in [ for t in [
("Supplier Quotation", "price_list_name", "buying_price_list"), ("Supplier Quotation", "price_list_name", "buying_price_list"),
("Purchase Order", "price_list_name", "buying_price_list"), ("Purchase Order", "price_list_name", "buying_price_list"),
@ -23,7 +25,7 @@ def execute():
if t[2] in table_columns and t[1] in table_columns: if t[2] in table_columns and t[1] in table_columns:
# already reloaded, so copy into new column and drop old column # already reloaded, so copy into new column and drop old column
webnotes.conn.sql("""update `tab%s` set `%s`=`%s`""" % (t[0], t[2], t[1])) webnotes.conn.sql("""update `tab%s` set `%s`=`%s`""" % (t[0], t[2], t[1]))
webnotes.conn.sql("""alter table `tab%s` drop column `%s`""" % (t[0], t[1])) webnotes.conn.sql_ddl("""alter table `tab%s` drop column `%s`""" % (t[0], t[1]))
elif t[1] in table_columns: elif t[1] in table_columns:
webnotes.conn.sql_ddl("alter table `tab%s` change `%s` `%s` varchar(180)" % t) webnotes.conn.sql_ddl("alter table `tab%s` change `%s` `%s` varchar(180)" % t)

View File

@ -1,11 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import webnotes
def execute():
for employee in webnotes.conn.sql_list("""select name from `tabEmployee` where ifnull(date_of_birth, '')!=''"""):
obj = webnotes.get_obj("Employee", employee)
obj.update_dob_event()

View File

@ -0,0 +1,14 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import webnotes
def execute():
webnotes.reload_doc("core", "doctype", "event")
webnotes.conn.sql("""delete from `tabEvent` where repeat_on='Every Year' and ref_type='Employee'""")
for employee in webnotes.conn.sql_list("""select name from `tabEmployee` where status='Active' and
ifnull(date_of_birth, '')!=''"""):
obj = webnotes.get_obj("Employee", employee)
obj.update_dob_event()

View File

@ -0,0 +1,5 @@
import webnotes
def execute():
webnotes.conn.sql("""update `tabSerial No` set status = 'Not Available' where status='Not In Store'""")
webnotes.conn.sql("""update `tabSerial No` set status = 'Available' where status='In Store'""")

View File

@ -0,0 +1,117 @@
import webnotes
cancelled = []
uncancelled = []
def execute():
global cancelled, uncancelled
stock_entries = webnotes.conn.sql("""select * from `tabStock Entry`
where docstatus >= 1 and date(modified) >= "2013-08-16"
and ifnull(production_order, '') != '' and ifnull(bom_no, '') != ''
order by modified desc, name desc""", as_dict=True)
for entry in stock_entries:
if not webnotes.conn.sql("""select name from `tabStock Entry Detail`
where parent=%s""", entry.name):
res = webnotes.conn.sql("""select * from `tabStock Ledger Entry`
where voucher_type='Stock Entry' and voucher_no=%s
and is_cancelled='No'""", entry.name, as_dict=True)
if res:
make_stock_entry_detail(entry, res)
if cancelled or uncancelled:
send_email()
def make_stock_entry_detail(entry, res):
global cancelled, uncancelled
fg_item = webnotes.conn.get_value("Production Order", entry.production_order,
"production_item")
voucher_detail_entries_map = {}
for sle in res:
voucher_detail_entries_map.setdefault(sle.voucher_detail_no, []).append(sle)
for i, voucher_detail_no in enumerate(sorted(voucher_detail_entries_map.keys())):
sl_entries = voucher_detail_entries_map[voucher_detail_no]
# create stock entry details back from stock ledger entries
stock_entry_detail = webnotes.doc({
"doctype": "Stock Entry Detail",
"parentfield": "mtn_details",
"parenttype": "Stock Entry",
"parent": entry.name,
"__islocal": 1,
"idx": i+1,
"docstatus": 1,
"owner": entry.owner,
"name": voucher_detail_no,
"transfer_qty": abs(sl_entries[0].actual_qty),
"qty": abs(sl_entries[0].actual_qty),
"stock_uom": sl_entries[0].stock_uom,
"uom": sl_entries[0].stock_uom,
"conversion_factor": 1,
"item_code": sl_entries[0].item_code,
"description": webnotes.conn.get_value("Item", sl_entries[0].item_code,
"description"),
"incoming_rate": sl_entries[0].incoming_rate,
"batch_no": sl_entries[0].batch_no,
"serial_no": sl_entries[0].serial_no
})
if sl_entries[0].item_code == fg_item:
stock_entry_detail.bom_no = entry.bom_no
for sle in sl_entries:
if sle.actual_qty < 0:
stock_entry_detail.s_warehouse = sle.warehouse
else:
stock_entry_detail.t_warehouse = sle.warehouse
stock_entry_detail.save()
if entry.docstatus == 2:
webnotes.conn.set_value("Stock Entry", entry.name, "docstatus", 1)
# call for cancelled ones
se = webnotes.bean("Stock Entry", entry.name)
controller = se.make_controller()
controller.update_production_order(1)
res = webnotes.conn.sql("""select name from `tabStock Entry`
where amended_from=%s""", entry.name)
if res:
cancelled.append(res[0][0])
if res[0][0] in uncancelled:
uncancelled.remove(res[0][0])
webnotes.bean("Stock Entry", res[0][0]).cancel()
uncancelled.append(se.doc.name)
def send_email():
from webnotes.utils.email_lib import sendmail_to_system_managers
global cancelled, uncancelled
uncancelled = "we have undone the cancellation of the following Stock Entries through a patch:\n" + \
"\n".join(uncancelled) if uncancelled else ""
cancelled = "and cancelled the following Stock Entries:\n" + "\n".join(cancelled) \
if cancelled else ""
subject = "[ERPNext] [Important] Cancellation undone for some Stock Entries"
content = """Dear System Manager,
An error got introduced into the code that cleared the item table in a Stock Entry associated to a Production Order.
To undo its effect,
%s
%s
You will have to edit them again.
Sorry for the inconvenience this has caused.
Regards,
Team ERPNext.""" % (uncancelled, cancelled)
# print subject, content
sendmail_to_system_managers(subject, content)

View File

@ -13,7 +13,7 @@ def execute():
where account = %s and voucher_type = 'Sales Invoice' and voucher_no = %s where account = %s and voucher_type = 'Sales Invoice' and voucher_no = %s
and ifnull(is_cancelled, 'No') = 'No' limit 1""", (r.debit_to, r.name), as_dict=1) and ifnull(is_cancelled, 'No') = 'No' limit 1""", (r.debit_to, r.name), as_dict=1)
if gle: if gle:
diff = round((flt(r.grand_total) - flt(gle[0]['debit'])), 2) diff = flt((flt(r.grand_total) - flt(gle[0]['debit'])), 2)
if abs(diff) == 0.01: if abs(diff) == 0.01:
# print r.name, r.grand_total, gle[0]['debit'], diff # print r.name, r.grand_total, gle[0]['debit'], diff

View File

@ -39,7 +39,7 @@ def execute():
status = 'Not in Use' status = 'Not in Use'
if sle and flt(sle[0]['actual_qty']) > 0: if sle and flt(sle[0]['actual_qty']) > 0:
status = 'In Store' status = 'Available'
elif sle and flt(sle[0]['actual_qty']) < 0: elif sle and flt(sle[0]['actual_qty']) < 0:
status = 'Delivered' status = 'Delivered'

View File

@ -13,17 +13,27 @@ def execute():
name = question.question[:180] name = question.question[:180]
if webnotes.conn.exists("Note", name): if webnotes.conn.exists("Note", name):
webnotes.delete_doc("Note", name) webnotes.delete_doc("Note", name)
note = webnotes.bean({
similar_questions = webnotes.conn.sql_list("""select name from `tabQuestion`
where question like %s""", "%s%%" % name)
answers = [markdown2.markdown(c) for c in webnotes.conn.sql_list("""
select answer from tabAnswer where question in (%s)""" % \
", ".join(["%s"]*len(similar_questions)), similar_questions)]
webnotes.bean({
"doctype":"Note", "doctype":"Note",
"title": name, "title": name,
"content": "<hr>".join([markdown2.markdown(c) for c in webnotes.conn.sql_list(""" "content": "<hr>".join(answers),
select answer from tabAnswer where question=%s""", question.name)]),
"owner": question.owner, "owner": question.owner,
"creation": question.creation, "creation": question.creation,
"public": 1 "public": 1
}).insert() }).insert()
except NameError: except NameError:
pass pass
except Exception, e:
if e.args[0] != 1062:
raise e
webnotes.delete_doc("DocType", "Question") webnotes.delete_doc("DocType", "Question")
webnotes.delete_doc("DocType", "Answer") webnotes.delete_doc("DocType", "Answer")

View File

@ -22,7 +22,6 @@ patch_list = [
"patches.april_2012.update_role_in_address", "patches.april_2012.update_role_in_address",
"patches.april_2012.update_permlevel_in_address", "patches.april_2012.update_permlevel_in_address",
"patches.april_2012.update_appraisal_permission", "patches.april_2012.update_appraisal_permission",
"patches.april_2012.serial_no_fixes",
"patches.april_2012.repost_stock_for_posting_time", "patches.april_2012.repost_stock_for_posting_time",
"patches.may_2012.cleanup_property_setter", "patches.may_2012.cleanup_property_setter",
"patches.may_2012.rename_prev_doctype", "patches.may_2012.rename_prev_doctype",
@ -229,7 +228,6 @@ patch_list = [
"execute:webnotes.delete_doc('Report', 'Received Items To Be Billed')", "execute:webnotes.delete_doc('Report', 'Received Items To Be Billed')",
"patches.july_2013.p02_copy_shipping_address", "patches.july_2013.p02_copy_shipping_address",
"patches.july_2013.p03_cost_center_company", "patches.july_2013.p03_cost_center_company",
"execute:webnotes.bean('Style Settings').save() #2013-07-16",
"patches.july_2013.p04_merge_duplicate_leads", "patches.july_2013.p04_merge_duplicate_leads",
"patches.july_2013.p05_custom_doctypes_in_list_view", "patches.july_2013.p05_custom_doctypes_in_list_view",
"patches.july_2013.p06_same_sales_rate", "patches.july_2013.p06_same_sales_rate",
@ -254,5 +252,9 @@ patch_list = [
"patches.august_2013.p01_hr_settings", "patches.august_2013.p01_hr_settings",
"patches.august_2013.p02_rename_price_list", "patches.august_2013.p02_rename_price_list",
"patches.august_2013.p03_pos_setting_replace_customer_account", "patches.august_2013.p03_pos_setting_replace_customer_account",
"patches.august_2013.p04_employee_birthdays", "patches.august_2013.p05_update_serial_no_status",
"patches.august_2013.p05_employee_birthdays",
"execute:webnotes.reload_doc('accounts', 'Print Format', 'POS Invoice') # 2013-08-16",
"patches.august_2013.p06_fix_sle_against_stock_entry",
"execute:webnotes.bean('Style Settings').save() #2013-08-20",
] ]

View File

@ -6,9 +6,9 @@ wn.require("app/js/controllers/stock_controller.js");
erpnext.TransactionController = erpnext.stock.StockController.extend({ erpnext.TransactionController = erpnext.stock.StockController.extend({
onload: function() { onload: function() {
var me = this;
if(this.frm.doc.__islocal) { if(this.frm.doc.__islocal) {
var me = this, var today = get_today(),
today = get_today(),
currency = wn.defaults.get_default("currency"); currency = wn.defaults.get_default("currency");
$.each({ $.each({
@ -30,6 +30,14 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({
me.frm.script_manager.trigger("company"); me.frm.script_manager.trigger("company");
} }
if(this.other_fname) {
this[this.other_fname + "_remove"] = this.calculate_taxes_and_totals;
}
if(this.fname) {
this[this.fname + "_remove"] = this.calculate_taxes_and_totals;
}
}, },
onload_post_render: function() { onload_post_render: function() {
@ -311,8 +319,13 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({
function(item_code, tax_data) { function(item_code, tax_data) {
if(!item_tax[item_code]) item_tax[item_code] = {}; if(!item_tax[item_code]) item_tax[item_code] = {};
if($.isArray(tax_data)) { if($.isArray(tax_data)) {
var tax_rate = tax_data[0] == null ? "" : (flt(tax_data[0], tax_rate_precision) + "%"), var tax_rate = "";
tax_amount = format_currency(flt(tax_data[1], tax_amount_precision), company_currency, if(tax_data[0] != null) {
tax_rate = (tax.charge_type === "Actual") ?
format_currency(flt(tax_data[0], tax_amount_precision), company_currency, tax_amount_precision) :
(flt(tax_data[0], tax_rate_precision) + "%");
}
var tax_amount = format_currency(flt(tax_data[1], tax_amount_precision), company_currency,
tax_amount_precision); tax_amount_precision);
item_tax[item_code][tax.name] = [tax_rate, tax_amount]; item_tax[item_code][tax.name] = [tax_rate, tax_amount];

View File

@ -36,4 +36,47 @@ $.extend(erpnext, {
territory.territory = wn.defaults.get_default("territory"); territory.territory = wn.defaults.get_default("territory");
} }
}, },
setup_serial_no: function(grid_row) {
if(!grid_row.fields_dict.serial_no ||
grid_row.fields_dict.serial_no.get_status()!=="Write") return;
var $btn = $('<button class="btn btn-sm btn-default">Add Serial No</button>')
.appendTo($("<div>")
.css({"margin-bottom": "10px", "margin-top": "-10px"})
.appendTo(grid_row.fields_dict.serial_no.$wrapper));
$btn.on("click", function() {
var d = new wn.ui.Dialog({
title: "Add Serial No",
fields: [
{
"fieldtype": "Link",
"options": "Serial No",
"label": "Serial No",
"get_query": {
item_code: grid_row.doc.item_code,
warehouse: grid_row.doc.warehouse
}
},
{
"fieldtype": "Button",
"label": "Add"
}
]
});
d.get_input("add").on("click", function() {
var serial_no = d.get_value("serial_no");
if(serial_no) {
var val = (grid_row.doc.serial_no || "").split("\n").concat([serial_no]).join("\n");
grid_row.fields_dict.serial_no.set_model_value(val.trim());
}
d.hide();
return false;
});
d.show();
});
}
}); });

View File

@ -77,9 +77,9 @@ class DocType(TransactionBase):
msgprint("Please Select Company under which you want to create account head") msgprint("Please Select Company under which you want to create account head")
def update_credit_days_limit(self): def update_credit_days_limit(self):
sql("""update tabAccount set credit_days = %s, credit_limit = %s webnotes.conn.sql("""update tabAccount set credit_days = %s, credit_limit = %s
where name = %s""", (self.doc.credit_days or 0, self.doc.credit_limit or 0, where master_type='Customer' and master_name = %s""",
self.doc.name + " - " + self.get_company_abbr())) (self.doc.credit_days or 0, self.doc.credit_limit or 0, self.doc.name))
def create_lead_address_contact(self): def create_lead_address_contact(self):
if self.doc.lead_name: if self.doc.lead_name:

View File

@ -105,7 +105,6 @@ class DocType(TransactionBase):
msgprint("Please fetch items from Delivery Note selected", raise_exception=1) msgprint("Please fetch items from Delivery Note selected", raise_exception=1)
def on_update(self): def on_update(self):
get_obj("Stock Ledger").scrub_serial_nos(self, 'installed_item_details')
webnotes.conn.set(self.doc, 'status', 'Draft') webnotes.conn.set(self.doc, 'status', 'Draft')
def on_submit(self): def on_submit(self):

View File

@ -9,17 +9,8 @@ from core.doctype.communication.communication import make
def add_sales_communication(subject, content, sender, real_name, mail=None, def add_sales_communication(subject, content, sender, real_name, mail=None,
status="Open", date=None): status="Open", date=None):
def set_status(doctype, name):
w = webnotes.bean(doctype, name)
w.ignore_permissions = True
w.doc.status = is_system_user and "Replied" or status
w.doc.save()
if mail:
mail.save_attachments_in_doc(w.doc)
lead_name = webnotes.conn.get_value("Lead", {"email_id": sender}) lead_name = webnotes.conn.get_value("Lead", {"email_id": sender})
contact_name = webnotes.conn.get_value("Contact", {"email_id": sender}) contact_name = webnotes.conn.get_value("Contact", {"email_id": sender})
is_system_user = webnotes.conn.get_value("Profile", sender)
if not (lead_name or contact_name): if not (lead_name or contact_name):
# none, create a new Lead # none, create a new Lead
@ -34,14 +25,13 @@ def add_sales_communication(subject, content, sender, real_name, mail=None,
lead.insert() lead.insert()
lead_name = lead.doc.name lead_name = lead.doc.name
make(content=content, sender=sender, subject=subject, message = make(content=content, sender=sender, subject=subject,
lead=lead_name, contact=contact_name, date=date) lead=lead_name, contact=contact_name, date=date)
if contact_name: if mail:
set_status("Contact", contact_name) # save attachments to parent if from mail
elif lead_name: bean = webnotes.bean("Contact" if contact_name else "Lead", contact_name or lead_name)
set_status("Lead", lead_name) mail.save_attachments_in_doc(bean.doc)
class SalesMailbox(POP3Mailbox): class SalesMailbox(POP3Mailbox):
def setup(self, args=None): def setup(self, args=None):

View File

@ -39,17 +39,14 @@ class DocType(SellingController):
def validate(self): def validate(self):
if self.doc.status == 'Lead Lost' and not self.doc.order_lost_reason: if self.doc.status == 'Lead Lost' and not self.doc.order_lost_reason:
msgprint("Please Enter Lost Reason under More Info section") webnotes.throw("Please Enter Lost Reason under More Info section")
raise Exception
if self.doc.source == 'Campaign' and not self.doc.campaign_name and session['user'] != 'Guest': if self.doc.source == 'Campaign' and not self.doc.campaign_name and session['user'] != 'Guest':
msgprint("Please specify campaign name") webnotes.throw("Please specify campaign name")
raise Exception
if self.doc.email_id: if self.doc.email_id:
if not validate_email_add(self.doc.email_id): if not validate_email_add(self.doc.email_id):
msgprint('Please enter valid email id.') webnotes.throw('Please enter valid email id.')
raise Exception
def on_update(self): def on_update(self):
self.check_email_id_is_unique() self.check_email_id_is_unique()

View File

@ -77,7 +77,7 @@ class DocType:
def get_new_item_code(doctype, txt, searchfield, start, page_len, filters): def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
from controllers.queries import get_match_cond from controllers.queries import get_match_cond
return webnotes.conn.sql("""select name, description from tabItem return webnotes.conn.sql("""select name, item_name, description from tabItem
where is_stock_item="No" and is_sales_item="Yes" where is_stock_item="No" and is_sales_item="Yes"
and name not in (select name from `tabSales BOM`) and %s like %s and name not in (select name from `tabSales BOM`) and %s like %s
%s limit %s, %s""" % (searchfield, "%s", %s limit %s, %s""" % (searchfield, "%s",

View File

@ -95,7 +95,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
return{ return{
query : "selling.doctype.sales_common.sales_common.get_batch_no", query : "selling.doctype.sales_common.sales_common.get_batch_no",
filters: { filters: {
'item': item.item_code, 'item_code': item.item_code,
'posting_date': me.frm.doc.posting_date 'posting_date': me.frm.doc.posting_date
} }
} }
@ -640,7 +640,7 @@ var set_sales_bom_help = function(doc) {
$(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true); $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true);
if (inList(['Delivery Note', 'Sales Invoice'], doc.doctype)) { if (inList(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
help_msg = "<div class='alert'> \ help_msg = "<div class='alert alert-warning'> \
For 'Sales BOM' items, warehouse, serial no and batch no \ For 'Sales BOM' items, warehouse, serial no and batch no \
will be considered from the 'Packing List' table. \ will be considered from the 'Packing List' table. \
If warehouse and batch no are same for all packing items for any 'Sales BOM' item, \ If warehouse and batch no are same for all packing items for any 'Sales BOM' item, \

View File

@ -340,7 +340,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
and batch_no like '%(txt)s' and batch_no like '%(txt)s'
and exists(select * from `tabBatch` and exists(select * from `tabBatch`
where name = sle.batch_no where name = sle.batch_no
and expiry_date >= '%(posting_date)s' and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s')
and docstatus != 2) and docstatus != 2)
%(mcond)s %(mcond)s
group by batch_no having sum(actual_qty) > 0 group by batch_no having sum(actual_qty) > 0
@ -353,7 +353,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
return webnotes.conn.sql("""select name from tabBatch return webnotes.conn.sql("""select name from tabBatch
where docstatus != 2 where docstatus != 2
and item = '%(item_code)s' and item = '%(item_code)s'
and expiry_date >= '%(posting_date)s' and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s')
and name like '%(txt)s' and name like '%(txt)s'
%(mcond)s %(mcond)s
order by name desc order by name desc

View File

@ -128,6 +128,7 @@ class DocType(SellingController):
self.validate_po() self.validate_po()
self.validate_uom_is_integer("stock_uom", "qty") self.validate_uom_is_integer("stock_uom", "qty")
self.validate_for_items() self.validate_for_items()
self.validate_warehouse_user()
sales_com_obj = get_obj(dt = 'Sales Common') sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_active_sales_items(self) sales_com_obj.check_active_sales_items(self)
sales_com_obj.check_conversion_rate(self) sales_com_obj.check_conversion_rate(self)
@ -147,6 +148,16 @@ class DocType(SellingController):
if not self.doc.billing_status: self.doc.billing_status = 'Not Billed' if not self.doc.billing_status: self.doc.billing_status = 'Not Billed'
if not self.doc.delivery_status: self.doc.delivery_status = 'Not Delivered' if not self.doc.delivery_status: self.doc.delivery_status = 'Not Delivered'
def validate_warehouse_user(self):
from stock.utils import validate_warehouse_user
warehouses = list(set([d.reserved_warehouse for d in
self.doclist.get({"doctype": self.tname}) if d.reserved_warehouse]))
for w in warehouses:
validate_warehouse_user(w)
def validate_with_previous_doc(self): def validate_with_previous_doc(self):
super(DocType, self).validate_with_previous_doc(self.tname, { super(DocType, self).validate_with_previous_doc(self.tname, {
"Quotation": { "Quotation": {

View File

@ -272,6 +272,29 @@ class TestSalesOrder(unittest.TestCase):
self.check_reserved_qty(sbom_test_records[0][2]["item_code"], self.check_reserved_qty(sbom_test_records[0][2]["item_code"],
so.doclist[1].reserved_warehouse, 20.0) so.doclist[1].reserved_warehouse, 20.0)
def test_warehouse_user(self):
webnotes.session.user = "test@example.com"
webnotes.bean("Profile", "test@example.com").get_controller()\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
webnotes.bean("Profile", "test2@example.com").get_controller()\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
from stock.utils import UserNotAllowedForWarehouse
so = webnotes.bean(copy = test_records[0])
so.doc.company = "_Test Company 1"
so.doc.conversion_rate = 0.02
so.doc.plc_conversion_rate = 0.02
so.doclist[1].reserved_warehouse = "_Test Warehouse 2 - _TC1"
self.assertRaises(UserNotAllowedForWarehouse, so.insert)
webnotes.session.user = "test2@example.com"
so.insert()
webnotes.session.user = "Administrator"
test_dependencies = ["Sales BOM"] test_dependencies = ["Sales BOM"]
test_records = [ test_records = [

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-03-07 11:42:58", "creation": "2013-03-07 11:42:58",
"docstatus": 0, "docstatus": 0,
"modified": "2013-08-07 14:44:50", "modified": "2013-08-22 15:43:07",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -230,7 +230,7 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 0, "in_list_view": 0,
"label": "Reserved Warehouse", "label": "Reserved Warehouse",
"no_copy": 1, "no_copy": 0,
"oldfieldname": "reserved_warehouse", "oldfieldname": "reserved_warehouse",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Warehouse", "options": "Warehouse",

View File

@ -69,10 +69,24 @@ def get_item_details(args):
if cint(args.is_pos): if cint(args.is_pos):
pos_settings = get_pos_settings(args.company) pos_settings = get_pos_settings(args.company)
if pos_settings:
out.update(apply_pos_settings(pos_settings, out)) out.update(apply_pos_settings(pos_settings, out))
if args.doctype in ("Sales Invoice", "Delivery Note"):
if item_bean.doc.has_serial_no and not args.serial_no:
out.serial_no = _get_serial_nos_by_fifo(args, item_bean)
return out return out
def _get_serial_nos_by_fifo(args, item_bean):
return "\n".join(webnotes.conn.sql_list("""select name from `tabSerial No`
where item_code=%(item_code)s and warehouse=%(warehouse)s and status='Available'
order by timestamp(purchase_date, purchase_time) asc limit %(qty)s""", {
"item_code": args.item_code,
"warehouse": args.warehouse,
"qty": cint(args.qty)
}))
def _get_item_code(barcode): def _get_item_code(barcode):
item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode) item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode)

View File

@ -2,7 +2,7 @@
{ {
"creation": "2012-12-20 12:50:49", "creation": "2012-12-20 12:50:49",
"docstatus": 0, "docstatus": 0,
"modified": "2013-07-05 14:37:59", "modified": "2013-08-22 15:36:43",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -184,6 +184,13 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Point of Sale" "label": "Point of Sale"
}, },
{
"description": "To enable <b>Point of Sale</b> view",
"doctype": "DocField",
"fieldname": "fs_pos_view",
"fieldtype": "Check",
"label": "POS View"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "production", "fieldname": "production",

View File

@ -41,15 +41,15 @@ wn.pages.Setup.make = function(wrapper) {
}) })
.appendTo(body); .appendTo(body);
$('<div class="col col-lg-1"></div>').appendTo(row); $('<div class="col-md-1"></div>').appendTo(row);
if(item.type==="Link") { if(item.type==="Link") {
var col = $('<div class="col col-lg-5"><b><a href="#' var col = $('<div class="col-md-5"><b><a href="#'
+item.route+'"><i class="'+item.icon+'"></i> ' +item.route+'"><i class="'+item.icon+'"></i> '
+item.title+'</a></b></div>').appendTo(row); +item.title+'</a></b></div>').appendTo(row);
} else { } else {
var col = $(repl('<div class="col col-lg-5">\ var col = $(repl('<div class="col-md-5">\
<span class="badge view-link">%(count)s</span>\ <span class="badge view-link">%(count)s</span>\
<b><i class="%(icon)s"></i>\ <b><i class="%(icon)s"></i>\
<a class="data-link">%(title)s</a></b>\ <a class="data-link">%(title)s</a></b>\
@ -73,9 +73,9 @@ wn.pages.Setup.make = function(wrapper) {
} }
if(dependency) if(dependency)
col.addClass("col-offset-1"); col.addClass("col-md-offset-1");
else else
$('<div class="col col-lg-1"></div>').appendTo(row); $('<div class="col-md-1"></div>').appendTo(row);
if(item.doctype) { if(item.doctype) {
var badge = col.find(".badge, .data-link") var badge = col.find(".badge, .data-link")
@ -94,7 +94,7 @@ wn.pages.Setup.make = function(wrapper) {
} }
// tree // tree
$links = $('<div class="col col-lg-5">').appendTo(row); $links = $('<div class="col-md-5">').appendTo(row);
if(item.tree) { if(item.tree) {
$('<a class="view-link"><i class="icon-sitemap"></i> Browse</a>\ $('<a class="view-link"><i class="icon-sitemap"></i> Browse</a>\

View File

@ -32,12 +32,13 @@ lang_names = {
"nederlands": "nl", "nederlands": "nl",
"српски":"sr", "српски":"sr",
"தமிழ்": "ta", "தமிழ்": "ta",
"Hrvatski": "hr", "hrvatski": "hr",
"italiano": "it",
"ไทย": "th", "ไทย": "th",
"العربية":"ar" "العربية":"ar"
} }
lang_list = ["ar", "de", "en", "hi", "es", "fr", "pt-BR", "pt", "nl", "hr", "th"] lang_list = ["ar", "de", "en", "es", "fr", "hi", "hr", "it", "nl", "pt-BR", "pt", "th", "sr", "ta"]
product_name = "ERPNext" product_name = "ERPNext"
profile_defaults = { profile_defaults = {

View File

@ -72,7 +72,7 @@ def feature_setup():
'fs_exports', 'fs_imports', 'fs_discounts', 'fs_purchase_discounts', 'fs_exports', 'fs_imports', 'fs_discounts', 'fs_purchase_discounts',
'fs_after_sales_installations', 'fs_projects', 'fs_sales_extras', 'fs_after_sales_installations', 'fs_projects', 'fs_sales_extras',
'fs_recurring_invoice', 'fs_pos', 'fs_manufacturing', 'fs_quality', 'fs_recurring_invoice', 'fs_pos', 'fs_manufacturing', 'fs_quality',
'fs_page_break', 'fs_more_info' 'fs_page_break', 'fs_more_info', 'fs_pos_view'
] ]
doc.fields.update(dict(zip(flds, [1]*len(flds)))) doc.fields.update(dict(zip(flds, [1]*len(flds))))
doc.save() doc.save()

View File

@ -75,6 +75,10 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
this.get_terms(); this.get_terms();
}, },
delivery_note_details_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no(grid_row)
}
}); });
// for backward compatibility: combine new and previous states // for backward compatibility: combine new and previous states

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes import webnotes
from webnotes.utils import cstr, flt, cint from webnotes.utils import cstr, flt, cint, add_days
from webnotes.model.bean import getlist from webnotes.model.bean import getlist
from webnotes.model.code import get_obj from webnotes.model.code import get_obj
from webnotes import msgprint, _ from webnotes import msgprint, _
@ -175,9 +175,6 @@ class DocType(SellingController):
def on_update(self): def on_update(self):
self.doclist = get_obj('Sales Common').make_packing_list(self,'delivery_note_details') self.doclist = get_obj('Sales Common').make_packing_list(self,'delivery_note_details')
sl = get_obj('Stock Ledger')
sl.scrub_serial_nos(self)
sl.scrub_serial_nos(self, 'packing_details')
def on_submit(self): def on_submit(self):
self.validate_packed_qty() self.validate_packed_qty()
@ -185,22 +182,12 @@ class DocType(SellingController):
# Check for Approving Authority # Check for Approving Authority
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self) get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self)
# validate serial no for item table (non-sales-bom item) and packing list (sales-bom item)
sl_obj = get_obj("Stock Ledger")
sl_obj.validate_serial_no(self, 'delivery_note_details')
sl_obj.validate_serial_no_warehouse(self, 'delivery_note_details')
sl_obj.validate_serial_no(self, 'packing_details')
sl_obj.validate_serial_no_warehouse(self, 'packing_details')
# update delivery details in serial no
sl_obj.update_serial_record(self, 'delivery_note_details', is_submit = 1, is_incoming = 0)
sl_obj.update_serial_record(self, 'packing_details', is_submit = 1, is_incoming = 0)
# update delivered qty in sales order # update delivered qty in sales order
self.update_prevdoc_status() self.update_prevdoc_status()
# create stock ledger entry # create stock ledger entry
self.update_stock_ledger(update_stock = 1) self.update_stock_ledger(update_stock = 1)
self.update_serial_nos()
self.credit_limit() self.credit_limit()
@ -211,6 +198,50 @@ class DocType(SellingController):
webnotes.conn.set(self.doc, 'status', 'Submitted') webnotes.conn.set(self.doc, 'status', 'Submitted')
def on_cancel(self):
sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_stop_sales_order(self)
self.check_next_docstatus()
self.update_prevdoc_status()
self.update_stock_ledger(update_stock = -1)
self.update_serial_nos(cancel=True)
webnotes.conn.set(self.doc, 'status', 'Cancelled')
self.cancel_packing_slips()
self.make_cancel_gl_entries()
def update_serial_nos(self, cancel=False):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
update_serial_nos_after_submit(self, "Delivery Note", "delivery_note_details")
update_serial_nos_after_submit(self, "Delivery Note", "packing_details")
for table_fieldname in ("delivery_note_details", "packing_details"):
for d in self.doclist.get({"parentfield": table_fieldname}):
for serial_no in get_serial_nos(d.serial_no):
sr = webnotes.bean("Serial No", serial_no)
if cancel:
sr.doc.status = "Available"
for fieldname in ("warranty_expiry_date", "delivery_document_type",
"delivery_document_no", "delivery_date", "delivery_time", "customer",
"customer_name"):
sr.doc.fields[fieldname] = None
else:
sr.doc.delivery_document_type = "Delivery Note"
sr.doc.delivery_document_no = self.doc.name
sr.doc.delivery_date = self.doc.posting_date
sr.doc.delivery_time = self.doc.posting_time
sr.doc.customer = self.doc.customer
sr.doc.customer_name = self.doc.customer_name
if sr.doc.warranty_period:
sr.doc.warranty_expiry_date = add_days(cstr(self.doc.posting_date),
cint(sr.doc.warranty_period))
sr.doc.status = 'Delivered'
sr.save()
def validate_packed_qty(self): def validate_packed_qty(self):
""" """
Validate that if packed qty exists, it should be equal to qty Validate that if packed qty exists, it should be equal to qty
@ -232,26 +263,6 @@ class DocType(SellingController):
+ ", Packed: " + cstr(d[2])) for d in packing_error_list]) + ", Packed: " + cstr(d[2])) for d in packing_error_list])
webnotes.msgprint("Packing Error:\n" + err_msg, raise_exception=1) webnotes.msgprint("Packing Error:\n" + err_msg, raise_exception=1)
def on_cancel(self):
sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_stop_sales_order(self)
self.check_next_docstatus()
# remove delivery details from serial no
sl = get_obj('Stock Ledger')
sl.update_serial_record(self, 'delivery_note_details', is_submit = 0, is_incoming = 0)
sl.update_serial_record(self, 'packing_details', is_submit = 0, is_incoming = 0)
self.update_prevdoc_status()
self.update_stock_ledger(update_stock = -1)
webnotes.conn.set(self.doc, 'status', 'Cancelled')
self.cancel_packing_slips()
self.make_cancel_gl_entries()
def check_next_docstatus(self): def check_next_docstatus(self):
submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name)) submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name))
if submit_rv: if submit_rv:
@ -263,7 +274,6 @@ class DocType(SellingController):
msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !") msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !")
raise Exception , "Validation Error." raise Exception , "Validation Error."
def cancel_packing_slips(self): def cancel_packing_slips(self):
""" """
Cancel submitted packing slips related to this delivery note Cancel submitted packing slips related to this delivery note

View File

@ -33,7 +33,7 @@ class TestDeliveryNote(unittest.TestCase):
self.assertEquals(len(si), len(dn.doclist)) self.assertEquals(len(si), len(dn.doclist))
# modify export_amount # modify export_amount
si[1].ref_rate = 200 si[1].export_rate = 200
self.assertRaises(webnotes.ValidationError, webnotes.bean(si).insert) self.assertRaises(webnotes.ValidationError, webnotes.bean(si).insert)
@ -97,6 +97,59 @@ class TestDeliveryNote(unittest.TestCase):
webnotes.defaults.set_global_default("auto_inventory_accounting", 0) webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
def test_serialized(self):
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
dn = webnotes.bean(copy=test_records[0])
dn.doclist[1].item_code = "_Test Serialized Item With Series"
dn.doclist[1].qty = 1
dn.doclist[1].serial_no = serial_nos[0]
dn.insert()
dn.submit()
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Delivered")
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0],
"delivery_document_no"), dn.doc.name)
return dn
def test_serialized_cancel(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
dn = self.test_serialized()
dn.cancel()
serial_nos = get_serial_nos(dn.doclist[1].serial_no)
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Available")
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0],
"delivery_document_no"))
def test_serialize_status(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
sr = webnotes.bean("Serial No", serial_nos[0])
sr.doc.status = "Not Available"
sr.save()
dn = webnotes.bean(copy=test_records[0])
dn.doclist[1].item_code = "_Test Serialized Item With Series"
dn.doclist[1].qty = 1
dn.doclist[1].serial_no = serial_nos[0]
dn.insert()
self.assertRaises(SerialNoStatusError, dn.submit)
test_records = [ test_records = [
[ [
{ {

View File

@ -272,4 +272,4 @@ class DocType(DocListController):
from stock.stock_ledger import update_entries_after from stock.stock_ledger import update_entries_after
for wh in webnotes.conn.sql("""select warehouse from `tabBin` for wh in webnotes.conn.sql("""select warehouse from `tabBin`
where item_code=%s""", newdn): where item_code=%s""", newdn):
update_entries_after({"item_code": newdn, "warehouse": wh}) update_entries_after({"item_code": newdn, "warehouse": wh[0]})

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-05-03 10:45:46", "creation": "2013-05-03 10:45:46",
"docstatus": 0, "docstatus": 0,
"modified": "2013-08-08 14:22:25", "modified": "2013-08-30 16:21:38",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -35,7 +35,9 @@
"parentfield": "permissions", "parentfield": "permissions",
"parenttype": "DocType", "parenttype": "DocType",
"permlevel": 0, "permlevel": 0,
"read": 1 "read": 1,
"report": 1,
"submit": 0
}, },
{ {
"doctype": "DocType", "doctype": "DocType",
@ -300,6 +302,14 @@
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{
"depends_on": "eval: doc.has_serial_no===\"Yes\"",
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
"doctype": "DocField",
"fieldname": "serial_no_series",
"fieldtype": "Data",
"label": "Serial Number Series"
},
{ {
"depends_on": "eval:doc.is_stock_item==\"Yes\"", "depends_on": "eval:doc.is_stock_item==\"Yes\"",
"doctype": "DocField", "doctype": "DocField",
@ -549,20 +559,6 @@
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{
"default": "No",
"depends_on": "eval:doc.is_sales_item==\"Yes\"",
"description": "Select \"Yes\" if this item is to be sent to a customer or received from a supplier as a sample. Delivery notes and Purchase Receipts will update stock levels but there will be no invoice against this item.",
"doctype": "DocField",
"fieldname": "is_sample_item",
"fieldtype": "Select",
"label": "Allow Samples",
"oldfieldname": "is_sample_item",
"oldfieldtype": "Select",
"options": "Yes\nNo",
"read_only": 0,
"reqd": 1
},
{ {
"depends_on": "eval:doc.is_sales_item==\"Yes\"", "depends_on": "eval:doc.is_sales_item==\"Yes\"",
"doctype": "DocField", "doctype": "DocField",
@ -870,9 +866,7 @@
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"doctype": "DocPerm", "doctype": "DocPerm",
"report": 1,
"role": "Material Master Manager", "role": "Material Master Manager",
"submit": 0,
"write": 1 "write": 1
}, },
{ {
@ -880,9 +874,7 @@
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"doctype": "DocPerm", "doctype": "DocPerm",
"report": 1,
"role": "Material Manager", "role": "Material Manager",
"submit": 0,
"write": 0 "write": 0
}, },
{ {
@ -890,21 +882,7 @@
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"doctype": "DocPerm", "doctype": "DocPerm",
"report": 1,
"role": "Material User", "role": "Material User",
"submit": 0,
"write": 0 "write": 0
},
{
"doctype": "DocPerm",
"role": "Sales User"
},
{
"doctype": "DocPerm",
"role": "Purchase User"
},
{
"doctype": "DocPerm",
"role": "Accounts User"
} }
] ]

View File

@ -44,7 +44,6 @@ test_records = [
"is_purchase_item": "Yes", "is_purchase_item": "Yes",
"is_sales_item": "Yes", "is_sales_item": "Yes",
"is_service_item": "No", "is_service_item": "No",
"is_sample_item": "No",
"inspection_required": "No", "inspection_required": "No",
"is_pro_applicable": "No", "is_pro_applicable": "No",
"is_sub_contracted_item": "No", "is_sub_contracted_item": "No",
@ -82,7 +81,6 @@ test_records = [
"is_purchase_item": "Yes", "is_purchase_item": "Yes",
"is_sales_item": "Yes", "is_sales_item": "Yes",
"is_service_item": "No", "is_service_item": "No",
"is_sample_item": "No",
"inspection_required": "No", "inspection_required": "No",
"is_pro_applicable": "No", "is_pro_applicable": "No",
"is_sub_contracted_item": "No", "is_sub_contracted_item": "No",
@ -108,7 +106,6 @@ test_records = [
"is_purchase_item": "Yes", "is_purchase_item": "Yes",
"is_sales_item": "Yes", "is_sales_item": "Yes",
"is_service_item": "No", "is_service_item": "No",
"is_sample_item": "No",
"inspection_required": "No", "inspection_required": "No",
"is_pro_applicable": "No", "is_pro_applicable": "No",
"is_sub_contracted_item": "No", "is_sub_contracted_item": "No",
@ -128,7 +125,6 @@ test_records = [
"is_purchase_item": "Yes", "is_purchase_item": "Yes",
"is_sales_item": "Yes", "is_sales_item": "Yes",
"is_service_item": "No", "is_service_item": "No",
"is_sample_item": "No",
"inspection_required": "No", "inspection_required": "No",
"is_pro_applicable": "No", "is_pro_applicable": "No",
"is_sub_contracted_item": "No", "is_sub_contracted_item": "No",
@ -149,7 +145,6 @@ test_records = [
"is_purchase_item": "Yes", "is_purchase_item": "Yes",
"is_sales_item": "Yes", "is_sales_item": "Yes",
"is_service_item": "No", "is_service_item": "No",
"is_sample_item": "No",
"inspection_required": "No", "inspection_required": "No",
"is_pro_applicable": "Yes", "is_pro_applicable": "Yes",
"is_sub_contracted_item": "Yes", "is_sub_contracted_item": "Yes",
@ -168,7 +163,6 @@ test_records = [
"is_purchase_item": "Yes", "is_purchase_item": "Yes",
"is_sales_item": "Yes", "is_sales_item": "Yes",
"is_service_item": "No", "is_service_item": "No",
"is_sample_item": "No",
"inspection_required": "No", "inspection_required": "No",
"is_pro_applicable": "No", "is_pro_applicable": "No",
"is_sub_contracted_item": "No", "is_sub_contracted_item": "No",
@ -188,7 +182,26 @@ test_records = [
"is_purchase_item": "Yes", "is_purchase_item": "Yes",
"is_sales_item": "Yes", "is_sales_item": "Yes",
"is_service_item": "No", "is_service_item": "No",
"is_sample_item": "No", "inspection_required": "No",
"is_pro_applicable": "No",
"is_sub_contracted_item": "No",
"stock_uom": "_Test UOM"
}],
[{
"doctype": "Item",
"item_code": "_Test Serialized Item With Series",
"item_name": "_Test Serialized Item With Series",
"description": "_Test Serialized Item",
"item_group": "_Test Item Group Desktops",
"is_stock_item": "Yes",
"default_warehouse": "_Test Warehouse - _TC",
"is_asset_item": "No",
"has_batch_no": "No",
"has_serial_no": "Yes",
"serial_no_series": "ABCD.#####",
"is_purchase_item": "Yes",
"is_sales_item": "Yes",
"is_service_item": "No",
"inspection_required": "No", "inspection_required": "No",
"is_pro_applicable": "No", "is_pro_applicable": "No",
"is_sub_contracted_item": "No", "is_sub_contracted_item": "No",

View File

@ -51,10 +51,9 @@ class DocType(BuyingController):
msgprint("You can raise indent of maximum qty: %s for item: %s against sales order: %s\n Anyway, you can add more qty in new row for the same item." % (actual_so_qty - already_indented, item, so_no), raise_exception=1) msgprint("You can raise indent of maximum qty: %s for item: %s against sales order: %s\n Anyway, you can add more qty in new row for the same item." % (actual_so_qty - already_indented, item, so_no), raise_exception=1)
def validate_schedule_date(self): def validate_schedule_date(self):
#:::::::: validate schedule date v/s indent date ::::::::::::
for d in getlist(self.doclist, 'indent_details'): for d in getlist(self.doclist, 'indent_details'):
if d.schedule_date < self.doc.transaction_date: if d.schedule_date < self.doc.transaction_date:
msgprint("Expected Schedule Date cannot be before Material Request Date") msgprint("Expected Date cannot be before Material Request Date")
raise Exception raise Exception
# Validate # Validate

View File

@ -103,7 +103,7 @@ cur_frm.cscript.calc_net_total_pkg = function(doc, ps_detail) {
net_weight_pkg += flt(item.net_weight) * flt(item.qty); net_weight_pkg += flt(item.net_weight) * flt(item.qty);
} }
doc.net_weight_pkg = roundNumber(net_weight_pkg, 2); doc.net_weight_pkg = _round(net_weight_pkg, 2);
if(!flt(doc.gross_weight_pkg)) { if(!flt(doc.gross_weight_pkg)) {
doc.gross_weight_pkg = doc.net_weight_pkg doc.gross_weight_pkg = doc.net_weight_pkg
} }

View File

@ -23,8 +23,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
this.show_stock_ledger(); this.show_stock_ledger();
this.show_general_ledger(); this.show_general_ledger();
} } else {
cur_frm.add_custom_button(wn._('From Purchase Order'), cur_frm.add_custom_button(wn._('From Purchase Order'),
function() { function() {
wn.model.map_current_doc({ wn.model.map_current_doc({
@ -39,7 +38,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
} }
}) })
}); });
}
if(wn.boot.control_panel.country == 'India') { if(wn.boot.control_panel.country == 'India') {
unhide_field(['challan_no', 'challan_date']); unhide_field(['challan_no', 'challan_date']);
@ -127,7 +126,7 @@ cur_frm.cscript.new_contact = function(){
cur_frm.fields_dict['purchase_receipt_details'].grid.get_field('project_name').get_query = function(doc, cdt, cdn) { cur_frm.fields_dict['purchase_receipt_details'].grid.get_field('project_name').get_query = function(doc, cdt, cdn) {
return{ return{
filters:[ filters:[
['project', 'status', 'not in', 'Completed, Cancelled'] ['Project', 'status', 'not in', 'Completed, Cancelled']
] ]
} }
} }

View File

@ -127,8 +127,6 @@ class DocType(BuyingController):
self.validate_inspection() self.validate_inspection()
self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("uom", ["qty", "received_qty"])
self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("stock_uom", "stock_qty")
get_obj('Stock Ledger').validate_serial_no(self, 'purchase_receipt_details')
self.validate_challan_no() self.validate_challan_no()
pc_obj = get_obj(dt='Purchase Common') pc_obj = get_obj(dt='Purchase Common')
@ -147,16 +145,6 @@ class DocType(BuyingController):
for d in getlist(self.doclist,'purchase_receipt_details'): for d in getlist(self.doclist,'purchase_receipt_details'):
d.rejected_warehouse = self.doc.rejected_warehouse d.rejected_warehouse = self.doc.rejected_warehouse
get_obj('Stock Ledger').scrub_serial_nos(self)
self.scrub_rejected_serial_nos()
def scrub_rejected_serial_nos(self):
for d in getlist(self.doclist, 'purchase_receipt_details'):
if d.rejected_serial_no:
d.rejected_serial_no = cstr(d.rejected_serial_no).strip().replace(',', '\n')
d.save()
def update_stock(self, is_submit): def update_stock(self, is_submit):
pc_obj = get_obj('Purchase Common') pc_obj = get_obj('Purchase Common')
self.values = [] self.values = []
@ -207,11 +195,6 @@ class DocType(BuyingController):
# make Stock Entry # make Stock Entry
def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0): def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0):
if rejected:
serial_no = cstr(d.rejected_serial_no).strip()
else:
serial_no = cstr(d.serial_no).strip()
self.values.append({ self.values.append({
'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code, 'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code,
'warehouse' : wh, 'warehouse' : wh,
@ -227,7 +210,7 @@ class DocType(BuyingController):
'fiscal_year' : self.doc.fiscal_year, 'fiscal_year' : self.doc.fiscal_year,
'is_cancelled' : (is_submit==1) and 'No' or 'Yes', 'is_cancelled' : (is_submit==1) and 'No' or 'Yes',
'batch_no' : cstr(d.batch_no).strip(), 'batch_no' : cstr(d.batch_no).strip(),
'serial_no' : serial_no, 'serial_no' : d.serial_no,
"project" : d.project_name "project" : d.project_name
}) })
@ -261,17 +244,31 @@ class DocType(BuyingController):
self.update_prevdoc_status() self.update_prevdoc_status()
# Update Serial Record
get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 1, is_incoming = 1)
# Update Stock # Update Stock
self.update_stock(is_submit = 1) self.update_stock(is_submit = 1)
self.update_serial_nos()
# Update last purchase rate # Update last purchase rate
purchase_controller.update_last_purchase_rate(self, 1) purchase_controller.update_last_purchase_rate(self, 1)
self.make_gl_entries() self.make_gl_entries()
def update_serial_nos(self, cancel=False):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
update_serial_nos_after_submit(self, "Purchase Receipt", "purchase_receipt_details")
for d in self.doclist.get({"parentfield": "purchase_receipt_details"}):
for serial_no in get_serial_nos(d.serial_no):
sr = webnotes.bean("Serial No", serial_no)
if cancel:
sr.doc.supplier = None
sr.doc.supplier_name = None
else:
sr.doc.supplier = self.doc.supplier
sr.doc.supplier_name = self.doc.supplier_name
sr.save()
def check_next_docstatus(self): def check_next_docstatus(self):
submit_rv = sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % (self.doc.name)) submit_rv = sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % (self.doc.name))
if submit_rv: if submit_rv:
@ -295,10 +292,10 @@ class DocType(BuyingController):
webnotes.conn.set(self.doc,'status','Cancelled') webnotes.conn.set(self.doc,'status','Cancelled')
# 3. Cancel Serial No # 3. Cancel Serial No
get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 0, is_incoming = 1)
# 4.Update Bin # 4.Update Bin
self.update_stock(is_submit = 0) self.update_stock(is_submit = 0)
self.update_serial_nos(cancel=True)
self.update_prevdoc_status() self.update_prevdoc_status()

View File

@ -1,22 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
// License: GNU General Public License v3. See license.txt
// render
wn.listview_settings['Purchase Receipt'] = {
add_fields: ["group_concat(`tabPurchase Receipt Item`.prevdoc_docname) \
as purchase_order_no"],
add_columns: [{"content":"purchase_order_no", width:"30%"}],
group_by: "`tabPurchase Receipt`.name",
prepare_data: function(data) {
if(data.purchase_order_no) {
data.purchase_order_no = $.unique(data.purchase_order_no.split(","));
var po_list = [];
$.each(data.purchase_order_no, function(i, v){
if(po_list.indexOf(v)==-1) po_list.push(
repl("<a href=\"#Form/Purchase Order/%(name)s\">%(name)s</a>",
{name: v}));
});
data.purchase_order_no = po_list.join(", ");
}
}
};

View File

@ -77,6 +77,29 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0) self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0)
self.assertEquals(len(pr.doclist.get({"parentfield": "pr_raw_material_details"})), 2) self.assertEquals(len(pr.doclist.get({"parentfield": "pr_raw_material_details"})), 2)
def test_serial_no_supplier(self):
pr = webnotes.bean(copy=test_records[0])
pr.doclist[1].item_code = "_Test Serialized Item With Series"
pr.doclist[1].qty = 1
pr.doclist[1].received_qty = 1
pr.insert()
pr.submit()
self.assertEquals(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
"supplier"), pr.doc.supplier)
return pr
def test_serial_no_cancel(self):
pr = self.test_serial_no_supplier()
pr.cancel()
self.assertFalse(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
"warehouse"))
self.assertEqual(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
"status"), "Not Available")
test_dependencies = ["BOM"] test_dependencies = ["BOM"]

View File

@ -1,69 +1,10 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
cur_frm.cscript.onload = function(doc, cdt, cdn) { cur_frm.add_fetch("customer", "customer_name", "customer_name")
if(!doc.status) set_multiple(cdt, cdn, {status:'In Store'}); cur_frm.add_fetch("supplier", "supplier_name", "supplier_name")
if(doc.__islocal) hide_field(['supplier_name','address_display'])
}
cur_frm.add_fetch("item_code", "item_name", "item_name")
cur_frm.cscript.refresh = function(doc, cdt, cdn) { cur_frm.add_fetch("item_code", "description", "description")
flds = ['status', 'item_code', 'warehouse', 'purchase_document_type', cur_frm.add_fetch("item_code", "item_group", "item_group")
'purchase_document_no', 'purchase_date', 'purchase_time', 'purchase_rate', cur_frm.add_fetch("item_code", "brand", "brand")
'supplier']
for(i=0;i<flds.length;i++) {
cur_frm.set_df_property(flds[i], 'read_only', doc.__islocal ? 0 : 1);
}
}
// item details
// -------------
cur_frm.add_fetch('item_code', 'item_name', 'item_name')
cur_frm.add_fetch('item_code', 'item_group', 'item_group')
cur_frm.add_fetch('item_code', 'brand', 'brand')
cur_frm.add_fetch('item_code', 'description', 'description')
cur_frm.add_fetch('item_code', 'warranty_period', 'warranty_period')
// customer
// ---------
cur_frm.add_fetch('customer', 'customer_name', 'customer_name')
cur_frm.add_fetch('customer', 'address', 'delivery_address')
cur_frm.add_fetch('customer', 'territory', 'territory')
// territory
// ----------
cur_frm.fields_dict['territory'].get_query = function(doc,cdt,cdn) {
return{
filters:{'is_group': "No"}
}
}
// Supplier
//-------------
cur_frm.cscript.supplier = function(doc,dt,dn) {
if(doc.supplier) return get_server_fields('get_default_supplier_address', JSON.stringify({supplier: doc.supplier}),'', doc, dt, dn, 1);
if(doc.supplier) unhide_field(['supplier_name','address_display']);
}
//item code
//----------
cur_frm.fields_dict['item_code'].get_query = function(doc,cdt,cdn) {
return{
query:"controllers.queries.item_query",
filters:{
'has_serial_no': 'Yes'
}
}
}
cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) {
return{
query:"controllers.queries.customer_query"
}
}
cur_frm.fields_dict.supplier.get_query = function(doc,cdt,cdn) {
return{
query:"controllers.queries.supplier_query"
}
}

View File

@ -10,10 +10,24 @@ from webnotes import msgprint, _
from controllers.stock_controller import StockController from controllers.stock_controller import StockController
class SerialNoCannotCreateDirectError(webnotes.ValidationError): pass
class SerialNoCannotCannotChangeError(webnotes.ValidationError): pass
class DocType(StockController): class DocType(StockController):
def __init__(self, doc, doclist=[]): def __init__(self, doc, doclist=[]):
self.doc = doc self.doc = doc
self.doclist = doclist self.doclist = doclist
self.via_stock_ledger = False
def validate(self):
if self.doc.fields.get("__islocal") and self.doc.warehouse:
webnotes.throw(_("New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"),
SerialNoCannotCreateDirectError)
self.validate_warranty_status()
self.validate_amc_status()
self.validate_warehouse()
self.validate_item()
def validate_amc_status(self): def validate_amc_status(self):
""" """
@ -31,79 +45,41 @@ class DocType(StockController):
def validate_warehouse(self): def validate_warehouse(self):
if self.doc.status=='In Store' and not self.doc.warehouse: if not self.doc.fields.get("__islocal"):
msgprint("Warehouse is mandatory if this Serial No is <b>In Store</b>", raise_exception=1) item_code, warehouse = webnotes.conn.get_value("Serial No",
self.doc.name, ["item_code", "warehouse"])
if item_code != self.doc.item_code:
webnotes.throw(_("Item Code cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
if not self.via_stock_ledger and warehouse != self.doc.warehouse:
webnotes.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
if not self.doc.warehouse and self.doc.status=="Available":
self.doc.status = "Not Available"
def validate_item(self): def validate_item(self):
""" """
Validate whether serial no is required for this item Validate whether serial no is required for this item
""" """
item = webnotes.conn.sql("select name, has_serial_no from tabItem where name = '%s'" % self.doc.item_code) item = webnotes.doc("Item", self.doc.item_code)
if not item: if item.has_serial_no!="Yes":
msgprint("Item is not exists in the system", raise_exception=1) webnotes.throw(_("Item must have 'Has Serial No' as 'Yes'") + ": " + self.doc.item_code)
elif item[0][1] == 'No':
msgprint("To proceed please select 'Yes' in 'Has Serial No' in Item master: '%s'" % self.doc.item_code, raise_exception=1)
def validate(self):
self.validate_warranty_status()
self.validate_amc_status()
self.validate_warehouse()
self.validate_item()
def on_update(self):
if self.doc.warehouse and self.doc.status == 'In Store' \
and cint(self.doc.sle_exists) == 0 and \
not webnotes.conn.sql("""select name from `tabStock Ledger Entry`
where serial_no = %s and ifnull(is_cancelled, 'No') = 'No'""", self.doc.name):
self.make_stock_ledger_entry(1)
webnotes.conn.set(self.doc, 'sle_exists', 1)
self.make_gl_entries()
def make_stock_ledger_entry(self, qty):
from webnotes.model.code import get_obj
values = [{
'item_code' : self.doc.item_code,
'warehouse' : self.doc.warehouse,
'posting_date' : self.doc.purchase_date or (self.doc.creation and self.doc.creation.split(' ')[0]) or nowdate(),
'posting_time' : self.doc.purchase_time or '00:00',
'voucher_type' : 'Serial No',
'voucher_no' : self.doc.name,
'voucher_detail_no' : '',
'actual_qty' : qty,
'stock_uom' : webnotes.conn.get_value('Item', self.doc.item_code, 'stock_uom'),
'incoming_rate' : self.doc.purchase_rate,
'company' : self.doc.company,
'fiscal_year' : self.doc.fiscal_year,
'is_cancelled' : 'No', # is_cancelled is always 'No' because while deleted it can not find creation entry if it not created directly, voucher no != serial no
'batch_no' : '',
'serial_no' : self.doc.name
}]
get_obj('Stock Ledger').update_stock(values)
self.doc.item_group = item.item_group
self.doc.description = item.description
self.doc.item_name = item.item_name
self.doc.brand = item.brand
self.doc.warranty_period = item.warranty_period
def on_trash(self): def on_trash(self):
if self.doc.status == 'Delivered': if self.doc.status == 'Delivered':
msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1) msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1)
elif self.doc.status == 'In Store': if self.doc.warehouse:
webnotes.conn.set(self.doc, 'status', 'Not in Use') webnotes.throw(_("Cannot delete Serial No in warehouse. First remove from warehouse, then delete.") + \
self.make_stock_ledger_entry(-1) ": " + self.doc.name)
if cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) \
and webnotes.conn.sql("""select name from `tabGL Entry`
where voucher_type=%s and voucher_no=%s and ifnull(is_cancelled, 'No')='No'""",
(self.doc.doctype, self.doc.name)):
self.make_gl_entries(cancel=True)
def on_cancel(self): def on_cancel(self):
self.on_trash() self.on_trash()
def on_restore(self):
self.make_stock_ledger_entry(1)
self.make_gl_entries()
def on_rename(self, new, old, merge=False): def on_rename(self, new, old, merge=False):
"""rename serial_no text fields""" """rename serial_no text fields"""
if merge: if merge:
@ -119,18 +95,3 @@ class DocType(StockController):
webnotes.conn.sql("""update `tab%s` set serial_no = %s webnotes.conn.sql("""update `tab%s` set serial_no = %s
where name=%s""" % (dt[0], '%s', '%s'), where name=%s""" % (dt[0], '%s', '%s'),
('\n'.join(serial_nos), item[0])) ('\n'.join(serial_nos), item[0]))
def make_gl_entries(self, cancel=False):
if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
return
from accounts.general_ledger import make_gl_entries
against_stock_account = self.get_company_default("stock_adjustment_account")
gl_entries = self.get_gl_entries_for_stock(against_stock_account, self.doc.purchase_rate)
for entry in gl_entries:
entry["posting_date"] = self.doc.purchase_date or (self.doc.creation and
self.doc.creation.split(' ')[0]) or nowdate()
if gl_entries:
make_gl_entries(gl_entries, cancel)

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-05-16 10:59:15", "creation": "2013-05-16 10:59:15",
"docstatus": 0, "docstatus": 0,
"modified": "2013-07-22 15:29:43", "modified": "2013-08-21 13:37:01",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -14,6 +14,7 @@
"doctype": "DocType", "doctype": "DocType",
"document_type": "Master", "document_type": "Master",
"icon": "icon-barcode", "icon": "icon-barcode",
"in_create": 0,
"module": "Stock", "module": "Stock",
"name": "__common__", "name": "__common__",
"search_fields": "item_code,status" "search_fields": "item_code,status"
@ -34,8 +35,7 @@
"parenttype": "DocType", "parenttype": "DocType",
"permlevel": 0, "permlevel": 0,
"read": 1, "read": 1,
"report": 1, "write": 1
"submit": 0
}, },
{ {
"doctype": "DocType", "doctype": "DocType",
@ -57,6 +57,7 @@
}, },
{ {
"default": "In Store", "default": "In Store",
"description": "Only Serial Nos with status \"Available\" can be delivered.",
"doctype": "DocField", "doctype": "DocField",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
@ -66,8 +67,8 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "status", "oldfieldname": "status",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nIn Store\nDelivered\nNot in Use\nPurchase Returned", "options": "\nAvailable\nNot Available\nDelivered\nPurchase Returned\nSales Returned",
"read_only": 1, "read_only": 0,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1
}, },
@ -98,6 +99,22 @@
"reqd": 1, "reqd": 1,
"search_index": 0 "search_index": 0
}, },
{
"description": "Warehouse can only be changed via Stock Entry / Delivery Note / Purchase Receipt",
"doctype": "DocField",
"fieldname": "warehouse",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Warehouse",
"no_copy": 1,
"oldfieldname": "warehouse",
"oldfieldtype": "Link",
"options": "Warehouse",
"read_only": 1,
"reqd": 0,
"search_index": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "column_break1", "fieldname": "column_break1",
@ -134,7 +151,7 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Item Group", "options": "Item Group",
"read_only": 1, "read_only": 1,
"reqd": 1, "reqd": 0,
"search_index": 0 "search_index": 0
}, },
{ {
@ -154,7 +171,7 @@
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_details", "fieldname": "purchase_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Purchase Details", "label": "Purchase / Manufacture Details",
"read_only": 0 "read_only": 0
}, },
{ {
@ -168,30 +185,30 @@
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_document_type", "fieldname": "purchase_document_type",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Purchase Document Type", "label": "Creation Document Type",
"no_copy": 1, "no_copy": 1,
"options": "\nPurchase Receipt\nStock Entry", "options": "\nPurchase Receipt\nStock Entry",
"read_only": 0 "read_only": 1
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_document_no", "fieldname": "purchase_document_no",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
"label": "Purchase Document No", "label": "Creation Document No",
"no_copy": 1, "no_copy": 1,
"read_only": 0 "read_only": 1
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_date", "fieldname": "purchase_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_filter": 1, "in_filter": 1,
"label": "Purchase Date", "label": "Creation Date",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "purchase_date", "oldfieldname": "purchase_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
"read_only": 0, "read_only": 1,
"reqd": 0, "reqd": 0,
"search_index": 0 "search_index": 0
}, },
@ -199,10 +216,10 @@
"doctype": "DocField", "doctype": "DocField",
"fieldname": "purchase_time", "fieldname": "purchase_time",
"fieldtype": "Time", "fieldtype": "Time",
"label": "Incoming Time", "label": "Creation Time",
"no_copy": 1, "no_copy": 1,
"read_only": 0, "read_only": 1,
"reqd": 1 "reqd": 0
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
@ -214,8 +231,8 @@
"oldfieldname": "purchase_rate", "oldfieldname": "purchase_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 0, "read_only": 1,
"reqd": 1, "reqd": 0,
"search_index": 0 "search_index": 0
}, },
{ {
@ -225,21 +242,6 @@
"read_only": 0, "read_only": 0,
"width": "50%" "width": "50%"
}, },
{
"doctype": "DocField",
"fieldname": "warehouse",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Warehouse",
"no_copy": 1,
"oldfieldname": "warehouse",
"oldfieldtype": "Link",
"options": "Warehouse",
"read_only": 0,
"reqd": 0,
"search_index": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "supplier", "fieldname": "supplier",
@ -259,14 +261,6 @@
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{
"doctype": "DocField",
"fieldname": "address_display",
"fieldtype": "Text",
"label": "Supplier Address",
"no_copy": 1,
"read_only": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "delivery_details", "fieldname": "delivery_details",
@ -275,13 +269,6 @@
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"read_only": 0 "read_only": 0
}, },
{
"doctype": "DocField",
"fieldname": "column_break4",
"fieldtype": "Column Break",
"read_only": 0,
"width": "50%"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "delivery_document_type", "fieldname": "delivery_document_type",
@ -301,15 +288,6 @@
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{
"doctype": "DocField",
"fieldname": "customer_address",
"fieldtype": "Text",
"label": "Customer Address",
"oldfieldname": "customer_address",
"oldfieldtype": "Text",
"read_only": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "delivery_date", "fieldname": "delivery_date",
@ -359,7 +337,7 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Customer", "options": "Customer",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 0,
"search_index": 0 "search_index": 0
}, },
{ {
@ -374,28 +352,6 @@
"read_only": 1, "read_only": 1,
"search_index": 0 "search_index": 0
}, },
{
"doctype": "DocField",
"fieldname": "delivery_address",
"fieldtype": "Text",
"label": "Delivery Address",
"no_copy": 1,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "territory",
"fieldtype": "Link",
"in_filter": 1,
"label": "Territory",
"no_copy": 1,
"oldfieldname": "territory",
"oldfieldtype": "Link",
"options": "Territory",
"print_hide": 1,
"read_only": 1,
"report_hide": 0
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "warranty_amc_details", "fieldname": "warranty_amc_details",
@ -485,62 +441,30 @@
"in_filter": 1, "in_filter": 1,
"label": "Company", "label": "Company",
"options": "link:Company", "options": "link:Company",
"read_only": 0,
"reqd": 1,
"search_index": 1
},
{
"doctype": "DocField",
"fieldname": "fiscal_year",
"fieldtype": "Select",
"in_filter": 1,
"label": "Fiscal Year",
"options": "link:Fiscal Year",
"read_only": 0,
"reqd": 1,
"search_index": 1
},
{
"doctype": "DocField",
"fieldname": "trash_reason",
"fieldtype": "Small Text",
"label": "Trash Reason",
"oldfieldname": "trash_reason",
"oldfieldtype": "Small Text",
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "sle_exists",
"fieldtype": "Check",
"hidden": 1,
"label": "SLE Exists",
"no_copy": 1,
"print_hide": 1,
"read_only": 1, "read_only": 1,
"report_hide": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"doctype": "DocPerm", "doctype": "DocPerm",
"role": "Material Master Manager", "report": 1,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"doctype": "DocPerm",
"role": "Material Manager", "role": "Material Manager",
"write": 0 "submit": 0
}, },
{ {
"amend": 0, "amend": 0,
"cancel": 0, "cancel": 0,
"create": 1,
"doctype": "DocPerm",
"report": 1,
"role": "Material User",
"submit": 0
},
{
"create": 0, "create": 0,
"doctype": "DocPerm", "doctype": "DocPerm",
"role": "Material User", "role": "Accounts User"
"write": 0
} }
] ]

View File

@ -7,99 +7,23 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes, unittest import webnotes, unittest
class TestSerialNo(unittest.TestCase):
def test_aii_gl_entries_for_serial_no_in_store(self):
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
sr = webnotes.bean(copy=test_records[0])
sr.doc.serial_no = "_Test Serial No 1"
sr.insert()
stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company",
"stock_in_hand_account")
against_stock_account = webnotes.conn.get_value("Company", "_Test Company",
"stock_adjustment_account")
# check stock ledger entries
sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Serial No' and voucher_no = %s""", sr.doc.name, as_dict=1)[0]
self.assertTrue(sle)
self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty],
["_Test Serialized Item", "_Test Warehouse - _TC", 1.0])
# check gl entries
gl_entries = webnotes.conn.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
order by account desc""", sr.doc.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = [
[stock_in_hand_account, 1000.0, 0.0],
[against_stock_account, 0.0, 1000.0]
]
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit)
sr.load_from_db()
self.assertEquals(sr.doc.sle_exists, 1)
# save again
sr.save()
gl_entries = webnotes.conn.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
order by account desc""", sr.doc.name, as_dict=1)
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit)
# trash/cancel
sr.submit()
sr.cancel()
gl_count = webnotes.conn.sql("""select count(name) from `tabGL Entry`
where voucher_type='Serial No' and voucher_no=%s and ifnull(is_cancelled, 'No') = 'Yes'
order by account asc, name asc""", sr.doc.name)
self.assertEquals(gl_count[0][0], 4)
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
def test_aii_gl_entries_for_serial_no_delivered(self):
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
sr = webnotes.bean(copy=test_records[0])
sr.doc.serial_no = "_Test Serial No 2"
sr.doc.status = "Delivered"
sr.insert()
gl_entries = webnotes.conn.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
order by account desc""", sr.doc.name, as_dict=1)
self.assertFalse(gl_entries)
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
test_dependencies = ["Item"] test_dependencies = ["Item"]
test_records = [ test_records = []
[
{ from stock.doctype.serial_no.serial_no import *
"company": "_Test Company",
"doctype": "Serial No", class TestSerialNo(unittest.TestCase):
"serial_no": "_Test Serial No", def test_cannot_create_direct(self):
"status": "In Store", sr = webnotes.new_bean("Serial No")
"item_code": "_Test Serialized Item", sr.doc.item_code = "_Test Serialized Item"
"item_group": "_Test Item Group", sr.doc.warehouse = "_Test Warehouse - _TC"
"warehouse": "_Test Warehouse - _TC", sr.doc.serial_no = "_TCSER0001"
"purchase_rate": 1000.0, sr.doc.purchase_rate = 10
"purchase_time": "11:37:39", self.assertRaises(SerialNoCannotCreateDirectError, sr.insert)
"purchase_date": "2013-02-26",
'fiscal_year': "_Test Fiscal Year 2013" sr.doc.warehouse = None
} sr.insert()
] self.assertTrue(sr.doc.name)
]
sr.doc.warehouse = "_Test Warehouse - _TC"
self.assertTrue(SerialNoCannotCannotChangeError, sr.doc.save)

View File

@ -103,12 +103,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}, },
callback: function(r) { callback: function(r) {
if (!r.exc) me.frm.set_value("expense_adjustment_account", r.message); if (!r.exc) me.frm.set_value("expense_adjustment_account", r.message);
me.get_items();
} }
}); });
} else {
me.get_items();
} }
}, },
@ -122,8 +118,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}, },
get_items: function() { get_items: function() {
if(this.frm.doc.__islocal && (this.frm.doc.production_order || this.frm.doc.bom_no) if(this.frm.doc.production_order || this.frm.doc.bom_no) {
&& !getchildren('Stock Entry Detail', this.frm.doc.name, 'mtn_details').length) {
// if production order / bom is mentioned, get items // if production order / bom is mentioned, get items
return this.frm.call({ return this.frm.call({
doc: this.frm.doc, doc: this.frm.doc,
@ -223,6 +218,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse; if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse; if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
}, },
mtn_details_on_form_rendered: function(doc, grid_row) {
erpnext.setup_serial_no(grid_row)
}
}); });
cur_frm.script_manager.make(erpnext.stock.StockEntry); cur_frm.script_manager.make(erpnext.stock.StockEntry);

View File

@ -20,6 +20,7 @@ sql = webnotes.conn.sql
class NotUpdateStockError(webnotes.ValidationError): pass class NotUpdateStockError(webnotes.ValidationError): pass
class StockOverReturnError(webnotes.ValidationError): pass class StockOverReturnError(webnotes.ValidationError): pass
class IncorrectValuationRateError(webnotes.ValidationError): pass class IncorrectValuationRateError(webnotes.ValidationError): pass
class DuplicateEntryForProductionOrderError(webnotes.ValidationError): pass
from controllers.stock_controller import StockController from controllers.stock_controller import StockController
@ -32,7 +33,6 @@ class DocType(StockController):
def validate(self): def validate(self):
self.validate_posting_time() self.validate_posting_time()
self.validate_purpose() self.validate_purpose()
self.validate_serial_nos()
pro_obj = self.doc.production_order and \ pro_obj = self.doc.production_order and \
get_obj('Production Order', self.doc.production_order) or None get_obj('Production Order', self.doc.production_order) or None
@ -52,14 +52,14 @@ class DocType(StockController):
self.set_total_amount() self.set_total_amount()
def on_submit(self): def on_submit(self):
self.update_serial_no(1)
self.update_stock_ledger(0) self.update_stock_ledger(0)
self.update_serial_no(1)
self.update_production_order(1) self.update_production_order(1)
self.make_gl_entries() self.make_gl_entries()
def on_cancel(self): def on_cancel(self):
self.update_serial_no(0)
self.update_stock_ledger(1) self.update_stock_ledger(1)
self.update_serial_no(0)
self.update_production_order(0) self.update_production_order(0)
self.make_cancel_gl_entries() self.make_cancel_gl_entries()
@ -75,11 +75,6 @@ class DocType(StockController):
msgprint(_("Purpose must be one of ") + comma_or(valid_purposes), msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
raise_exception=True) raise_exception=True)
def validate_serial_nos(self):
sl_obj = get_obj("Stock Ledger")
sl_obj.scrub_serial_nos(self)
sl_obj.validate_serial_no(self, 'mtn_details')
def validate_item(self): def validate_item(self):
for item in self.doclist.get({"parentfield": "mtn_details"}): for item in self.doclist.get({"parentfield": "mtn_details"}):
if item.item_code not in self.stock_items: if item.item_code not in self.stock_items:
@ -152,21 +147,33 @@ class DocType(StockController):
return return
if self.doc.purpose == "Manufacture/Repack": if self.doc.purpose == "Manufacture/Repack":
if not flt(self.doc.fg_completed_qty): # check for double entry
msgprint(_("Manufacturing Quantity") + _(" is mandatory"), raise_exception=1) self.check_duplicate_entry_for_production_order()
if flt(pro_obj.doc.qty) < (flt(pro_obj.doc.produced_qty)
+ flt(self.doc.fg_completed_qty)):
# do not allow manufacture of qty > production order qty
msgprint(_("For Item ") + pro_obj.doc.production_item
+ _("Quantity already manufactured")
+ " = %s." % flt(pro_obj.doc.produced_qty)
+ _("Hence, maximum allowed Manufacturing Quantity")
+ " = %s." % (flt(pro_obj.doc.qty) - flt(pro_obj.doc.produced_qty)),
raise_exception=1)
elif self.doc.purpose != "Material Transfer": elif self.doc.purpose != "Material Transfer":
self.doc.production_order = None self.doc.production_order = None
def check_duplicate_entry_for_production_order(self):
other_ste = [t[0] for t in webnotes.conn.get_values("Stock Entry", {
"production_order": self.doc.production_order,
"purpose": self.doc.purpose,
"docstatus": ["!=", 2],
"name": ["!=", self.doc.name]
}, "name")]
if other_ste:
production_item, qty = webnotes.conn.get_value("Production Order",
self.doc.production_order, ["production_item", "qty"])
args = other_ste + [production_item]
fg_qty_already_entered = webnotes.conn.sql("""select sum(actual_qty)
from `tabStock Entry Detail`
where parent in (%s)
and item_code = %s
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
if fg_qty_already_entered >= qty:
webnotes.throw(_("Stock Entries already created for Production Order ")
+ self.doc.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError)
def set_total_amount(self): def set_total_amount(self):
self.doc.total_amount = sum([flt(item.amount) for item in self.doclist.get({"parentfield": "mtn_details"})]) self.doc.total_amount = sum([flt(item.amount) for item in self.doclist.get({"parentfield": "mtn_details"})])
@ -206,7 +213,7 @@ class DocType(StockController):
"posting_date": self.doc.posting_date, "posting_date": self.doc.posting_date,
"posting_time": self.doc.posting_time, "posting_time": self.doc.posting_time,
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty, "qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
"serial_no": cstr(d.serial_no).strip(), "serial_no": d.serial_no,
"bom_no": d.bom_no, "bom_no": d.bom_no,
}) })
# get actual stock at source warehouse # get actual stock at source warehouse
@ -317,27 +324,21 @@ class DocType(StockController):
def update_serial_no(self, is_submit): def update_serial_no(self, is_submit):
"""Create / Update Serial No""" """Create / Update Serial No"""
from stock.utils import get_valid_serial_nos
sl_obj = get_obj('Stock Ledger') from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
if is_submit: update_serial_nos_after_submit(self, "Stock Entry", "mtn_details")
sl_obj.validate_serial_no_warehouse(self, 'mtn_details')
for d in getlist(self.doclist, 'mtn_details'): for d in getlist(self.doclist, 'mtn_details'):
if cstr(d.serial_no).strip(): for serial_no in get_serial_nos(d.serial_no):
for x in get_valid_serial_nos(d.serial_no):
serial_no = x.strip()
if d.s_warehouse:
sl_obj.update_serial_delivery_details(self, d, serial_no, is_submit)
if d.t_warehouse:
sl_obj.update_serial_purchase_details(self, d, serial_no, is_submit,
self.doc.purpose)
if self.doc.purpose == 'Purchase Return': if self.doc.purpose == 'Purchase Return':
serial_doc = Document("Serial No", serial_no) sr = webnotes.bean("Serial No", serial_no)
serial_doc.status = is_submit and 'Purchase Returned' or 'In Store' sr.doc.status = "Purchase Returned" if is_submit else "Available"
serial_doc.docstatus = is_submit and 2 or 0 sr.save()
serial_doc.save()
if self.doc.purpose == "Sales Return":
sr = webnotes.bean("Serial No", serial_no)
sr.doc.status = "Sales Returned" if is_submit else "Delivered"
sr.save()
def update_stock_ledger(self, is_cancelled=0): def update_stock_ledger(self, is_cancelled=0):
self.values = [] self.values = []
@ -571,7 +572,6 @@ class DocType(StockController):
for item in item_dict: for item in item_dict:
pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0) pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0)
desire_to_transfer = flt(self.doc.fg_completed_qty) * item_dict[item]["qty"] desire_to_transfer = flt(self.doc.fg_completed_qty) * item_dict[item]["qty"]
if desire_to_transfer <= pending_to_issue: if desire_to_transfer <= pending_to_issue:
item_dict[item]["qty"] = desire_to_transfer item_dict[item]["qty"] = desire_to_transfer
else: else:

View File

@ -7,6 +7,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes, unittest import webnotes, unittest
from webnotes.utils import flt from webnotes.utils import flt
from stock.doctype.stock_ledger_entry.stock_ledger_entry import *
class TestStockEntry(unittest.TestCase): class TestStockEntry(unittest.TestCase):
def tearDown(self): def tearDown(self):
@ -40,12 +41,43 @@ class TestStockEntry(unittest.TestCase):
webnotes.conn.set_default("company", self.old_default_company) webnotes.conn.set_default("company", self.old_default_company)
def test_warehouse_company_validation(self): def test_warehouse_company_validation(self):
webnotes.session.user = "test2@example.com"
webnotes.bean("Profile", "test2@example.com").get_controller()\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
from stock.doctype.stock_ledger_entry.stock_ledger_entry import InvalidWarehouseCompany from stock.doctype.stock_ledger_entry.stock_ledger_entry import InvalidWarehouseCompany
st1 = webnotes.bean(copy=test_records[0]) st1 = webnotes.bean(copy=test_records[0])
st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1" st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1"
st1.insert() st1.insert()
self.assertRaises(InvalidWarehouseCompany, st1.submit) self.assertRaises(InvalidWarehouseCompany, st1.submit)
webnotes.session.user = "Administrator"
def test_warehouse_user(self):
from stock.utils import UserNotAllowedForWarehouse
webnotes.session.user = "test@example.com"
webnotes.bean("Profile", "test@example.com").get_controller()\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
webnotes.bean("Profile", "test2@example.com").get_controller()\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
st1 = webnotes.bean(copy=test_records[0])
st1.doc.company = "_Test Company 1"
st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1"
st1.insert()
self.assertRaises(UserNotAllowedForWarehouse, st1.submit)
webnotes.session.user = "test2@example.com"
st1 = webnotes.bean(copy=test_records[0])
st1.doc.company = "_Test Company 1"
st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1"
st1.insert()
st1.submit()
webnotes.session.user = "Administrator"
def test_material_receipt_gl_entry(self): def test_material_receipt_gl_entry(self):
webnotes.conn.sql("delete from `tabStock Ledger Entry`") webnotes.conn.sql("delete from `tabStock Ledger Entry`")
webnotes.defaults.set_global_default("auto_inventory_accounting", 1) webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
@ -180,6 +212,7 @@ class TestStockEntry(unittest.TestCase):
def _clear_stock(self): def _clear_stock(self):
webnotes.conn.sql("delete from `tabStock Ledger Entry`") webnotes.conn.sql("delete from `tabStock Ledger Entry`")
webnotes.conn.sql("""delete from `tabBin`""") webnotes.conn.sql("""delete from `tabBin`""")
webnotes.conn.sql("""delete from `tabSerial No`""")
self.old_default_company = webnotes.conn.get_default("company") self.old_default_company = webnotes.conn.get_default("company")
webnotes.conn.set_default("company", "_Test Company") webnotes.conn.set_default("company", "_Test Company")
@ -571,12 +604,139 @@ class TestStockEntry(unittest.TestCase):
return se, pr.doc.name return se, pr.doc.name
def test_serial_no_not_reqd(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].serial_no = "ABCD"
se.insert()
self.assertRaises(SerialNoNotRequiredError, se.submit)
def test_serial_no_reqd(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].transfer_qty = 2
se.insert()
self.assertRaises(SerialNoRequiredError, se.submit)
def test_serial_no_qty_more(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].serial_no = "ABCD\nEFGH\nXYZ"
se.doclist[1].transfer_qty = 2
se.insert()
self.assertRaises(SerialNoQtyError, se.submit)
def test_serial_no_qty_less(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].serial_no = "ABCD"
se.doclist[1].transfer_qty = 2
se.insert()
self.assertRaises(SerialNoQtyError, se.submit)
def test_serial_no_transfer_in(self):
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].serial_no = "ABCD\nEFGH"
se.doclist[1].transfer_qty = 2
se.insert()
se.submit()
self.assertTrue(webnotes.conn.exists("Serial No", "ABCD"))
self.assertTrue(webnotes.conn.exists("Serial No", "EFGH"))
def test_serial_no_not_exists(self):
se = webnotes.bean(copy=test_records[0])
se.doc.purpose = "Material Issue"
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 2
se.doclist[1].s_warehouse = "_Test Warehouse 1 - _TC"
se.doclist[1].t_warehouse = None
se.doclist[1].serial_no = "ABCD\nEFGH"
se.doclist[1].transfer_qty = 2
se.insert()
self.assertRaises(SerialNoNotExistsError, se.submit)
def test_serial_by_series(self):
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
self.assertTrue(webnotes.conn.exists("Serial No", serial_nos[0]))
self.assertTrue(webnotes.conn.exists("Serial No", serial_nos[1]))
return se
def test_serial_item_error(self):
self.test_serial_by_series()
se = webnotes.bean(copy=test_records[0])
se.doc.purpose = "Material Transfer"
se.doclist[1].item_code = "_Test Serialized Item"
se.doclist[1].qty = 1
se.doclist[1].transfer_qty = 1
se.doclist[1].serial_no = "ABCD00001"
se.doclist[1].s_warehouse = "_Test Warehouse - _TC"
se.doclist[1].t_warehouse = "_Test Warehouse 1 - _TC"
se.insert()
self.assertRaises(SerialNoItemError, se.submit)
def test_serial_move(self):
se = make_serialized_item()
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
se = webnotes.bean(copy=test_records[0])
se.doc.purpose = "Material Transfer"
se.doclist[1].item_code = "_Test Serialized Item With Series"
se.doclist[1].qty = 1
se.doclist[1].transfer_qty = 1
se.doclist[1].serial_no = serial_no
se.doclist[1].s_warehouse = "_Test Warehouse - _TC"
se.doclist[1].t_warehouse = "_Test Warehouse 1 - _TC"
se.insert()
se.submit()
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC")
def test_serial_warehouse_error(self):
make_serialized_item()
se = webnotes.bean(copy=test_records[0])
se.doc.purpose = "Material Transfer"
se.doclist[1].item_code = "_Test Serialized Item With Series"
se.doclist[1].qty = 1
se.doclist[1].transfer_qty = 1
se.doclist[1].serial_no = "ABCD00001"
se.doclist[1].s_warehouse = "_Test Warehouse 1 - _TC"
se.doclist[1].t_warehouse = "_Test Warehouse - _TC"
se.insert()
self.assertRaises(SerialNoWarehouseError, se.submit)
def test_serial_cancel(self):
se = self.test_serial_by_series()
se.cancel()
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
self.assertFalse(webnotes.conn.get_value("Serial No", serial_no, "warehouse"))
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "status"), "Not Available")
def make_serialized_item():
se = webnotes.bean(copy=test_records[0])
se.doclist[1].item_code = "_Test Serialized Item With Series"
se.doclist[1].qty = 2
se.doclist[1].transfer_qty = 2
se.insert()
se.submit()
return se
test_records = [ test_records = [
[ [
{ {
"company": "_Test Company", "company": "_Test Company",
"doctype": "Stock Entry", "doctype": "Stock Entry",
"posting_date": "2013-01-25", "posting_date": "2013-01-01",
"posting_time": "17:14:24", "posting_time": "17:14:24",
"purpose": "Material Receipt", "purpose": "Material Receipt",
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": "_Test Fiscal Year 2013",

View File

@ -18,173 +18,9 @@ class DocType:
self.doc = doc self.doc = doc
self.doclist = doclist self.doclist = doclist
def scrub_serial_nos(self, obj, table_name = ''):
if not table_name:
table_name = obj.fname
for d in getlist(obj.doclist, table_name):
if d.serial_no:
serial_nos = cstr(d.serial_no).strip().replace(',', '\n').split('\n')
d.serial_no = "\n".join(map(lambda x: x.strip(), serial_nos))
d.save()
def validate_serial_no_warehouse(self, obj, fname):
for d in getlist(obj.doclist, fname):
wh = d.warehouse or d.s_warehouse
if cstr(d.serial_no).strip() and wh:
serial_nos = get_valid_serial_nos(d.serial_no)
for s in serial_nos:
s = s.strip()
sr_war = webnotes.conn.sql("select warehouse,name from `tabSerial No` where name = '%s'" % (s))
if not sr_war:
msgprint("Serial No %s does not exists"%s, raise_exception = 1)
elif not sr_war[0][0]:
msgprint("Warehouse not mentioned in the Serial No <b>%s</b>" % s, raise_exception = 1)
elif sr_war[0][0] != wh:
msgprint("Serial No : %s for Item : %s doesn't exists in Warehouse : %s" % (s, d.item_code, wh), raise_exception = 1)
def validate_serial_no(self, obj, fname):
"""check whether serial no is required"""
for d in getlist(obj.doclist, fname):
is_stock_item = webnotes.conn.get_value('Item', d.item_code, 'is_stock_item')
ar_required = webnotes.conn.get_value('Item', d.item_code, 'has_serial_no')
# [bug fix] need to strip serial nos of all spaces and new lines for validation
serial_no = cstr(d.serial_no).strip()
if serial_no:
if is_stock_item != 'Yes':
msgprint("Serial No is not required for non-stock item: %s" % d.item_code, raise_exception=1)
elif ar_required != 'Yes':
msgprint("If serial no required, please select 'Yes' in 'Has Serial No' in Item :" + d.item_code + \
', otherwise please remove serial no', raise_exception=1)
elif ar_required == 'Yes' and not serial_no and d.qty:
msgprint("Serial no is mandatory for item: "+ d.item_code, raise_exception = 1)
# validate rejected serial nos
if fname == 'purchase_receipt_details' and flt(d.rejected_qty) > 0 and ar_required == 'Yes' and not d.rejected_serial_no:
msgprint("Rejected serial no is mandatory for rejected qty of item: "+ d.item_code, raise_exception = 1)
def set_pur_serial_no_values(self, obj, serial_no, d, s, new_rec, rejected=None):
item_details = webnotes.conn.sql("""select item_group, warranty_period
from `tabItem` where name = '%s' and (ifnull(end_of_life,'')='' or
end_of_life = '0000-00-00' or end_of_life > now()) """ %(d.item_code), as_dict=1)
s.purchase_document_type = obj.doc.doctype
s.purchase_document_no = obj.doc.name
s.purchase_date = obj.doc.posting_date
s.purchase_time = obj.doc.posting_time
s.purchase_rate = d.valuation_rate or d.incoming_rate
s.item_code = d.item_code
s.item_name = d.item_name
s.brand = d.brand
s.description = d.description
s.item_group = item_details and item_details[0]['item_group'] or ''
s.warranty_period = item_details and item_details[0]['warranty_period'] or 0
s.supplier = obj.doc.supplier
s.supplier_name = obj.doc.supplier_name
s.address_display = obj.doc.address_display or obj.doc.supplier_address
s.warehouse = rejected and obj.doc.rejected_warehouse \
or d.warehouse or d.t_warehouse or ""
s.docstatus = 0
s.status = 'In Store'
s.modified = nowdate()
s.modified_by = session['user']
s.serial_no = serial_no
s.sle_exists = 1
s.company = obj.doc.company
s.save(new_rec)
def update_serial_purchase_details(self, obj, d, serial_no, is_submit, purpose = '', rejected=None):
exists = webnotes.conn.sql("select name, status, docstatus from `tabSerial No` where name = '%s'" % (serial_no))
if is_submit:
if exists and exists[0][2] != 2 and \
purpose not in ['Material Transfer', "Material Receipt", 'Sales Return']:
msgprint("Serial No: %s already %s" % (serial_no, exists and exists[0][1]), raise_exception = 1)
elif exists:
s = Document('Serial No', exists and exists[0][0])
self.set_pur_serial_no_values(obj, serial_no, d, s, new_rec = 0, rejected=rejected)
else:
s = Document('Serial No')
self.set_pur_serial_no_values(obj, serial_no, d, s, new_rec = 1, rejected=rejected)
else:
if exists and exists[0][1] == 'Delivered' and exists[0][2] != 2:
msgprint("Serial No: %s is already delivered, you can not cancel the document." % serial_no, raise_exception=1)
elif purpose == 'Material Transfer':
webnotes.conn.sql("update `tabSerial No` set status = 'In Store', purchase_document_type = '', purchase_document_no = '', warehouse = '%s' where name = '%s'" % (d.s_warehouse, serial_no))
elif purpose == 'Sales Return':
webnotes.conn.sql("update `tabSerial No` set status = 'Delivered', purchase_document_type = '', purchase_document_no = '' where name = '%s'" % serial_no)
else:
webnotes.conn.sql("update `tabSerial No` set docstatus = 2, status = 'Not in Use', purchase_document_type = '', purchase_document_no = '', purchase_date = null, purchase_rate = 0, supplier = null, supplier_name = '', address_display = '', warehouse = '' where name = '%s'" % serial_no)
def check_serial_no_exists(self, serial_no, item_code):
chk = webnotes.conn.sql("select name, status, docstatus, item_code from `tabSerial No` where name = %s", (serial_no), as_dict=1)
if not chk:
msgprint("Serial No: %s does not exists in the system" % serial_no, raise_exception=1)
elif chk and chk[0]['item_code'] != item_code:
msgprint("Serial No: %s not belong to item: %s" % (serial_no, item_code), raise_exception=1)
elif chk and chk[0]['docstatus'] == 2:
msgprint("Serial No: %s of Item : %s is trashed in the system" % (serial_no, item_code), raise_exception = 1)
elif chk and chk[0]['status'] == 'Delivered':
msgprint("Serial No: %s of Item : %s is already delivered." % (serial_no, item_code), raise_exception = 1)
def set_delivery_serial_no_values(self, obj, serial_no):
s = Document('Serial No', serial_no)
s.delivery_document_type = obj.doc.doctype
s.delivery_document_no = obj.doc.name
s.delivery_date = obj.doc.posting_date
s.delivery_time = obj.doc.posting_time
s.customer = obj.doc.customer
s.customer_name = obj.doc.customer_name
s.delivery_address = obj.doc.address_display
s.territory = obj.doc.territory
s.warranty_expiry_date = cint(s.warranty_period) and \
add_days(cstr(obj.doc.posting_date), cint(s.warranty_period)) or s.warranty_expiry_date
s.docstatus = 1
s.status = 'Delivered'
s.modified = nowdate()
s.modified_by = session['user']
s.save()
def update_serial_delivery_details(self, obj, d, serial_no, is_submit):
if is_submit:
self.check_serial_no_exists(serial_no, d.item_code)
self.set_delivery_serial_no_values(obj, serial_no)
else:
webnotes.conn.sql("update `tabSerial No` set docstatus = 0, status = 'In Store', delivery_document_type = '', delivery_document_no = '', delivery_date = null, customer = null, customer_name = '', delivery_address = '', territory = null where name = '%s'" % (serial_no))
def update_serial_record(self, obj, fname, is_submit = 1, is_incoming = 0):
for d in getlist(obj.doclist, fname):
if d.serial_no:
serial_nos = get_valid_serial_nos(d.serial_no)
for a in serial_nos:
serial_no = a.strip()
if is_incoming:
self.update_serial_purchase_details(obj, d, serial_no, is_submit)
else:
self.update_serial_delivery_details(obj, d, serial_no, is_submit)
if fname == 'purchase_receipt_details' and d.rejected_qty and d.rejected_serial_no:
serial_nos = get_valid_serial_nos(d.rejected_serial_no)
for a in serial_nos:
self.update_serial_purchase_details(obj, d, a, is_submit, rejected=True)
def update_stock(self, values, is_amended = 'No'): def update_stock(self, values, is_amended = 'No'):
for v in values: for v in values:
sle_id, valid_serial_nos = '', '' sle_id = ''
# get serial nos
if v.get("serial_no", "").strip():
valid_serial_nos = get_valid_serial_nos(v["serial_no"],
v['actual_qty'], v['item_code'])
v["serial_no"] = valid_serial_nos and "\n".join(valid_serial_nos) or ""
# reverse quantities for cancel # reverse quantities for cancel
if v.get('is_cancelled') == 'Yes': if v.get('is_cancelled') == 'Yes':

View File

@ -3,11 +3,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes import webnotes
from webnotes import _, msgprint from webnotes import _, msgprint, ValidationError
from webnotes.utils import cint, flt, getdate from webnotes.utils import cint, flt, getdate, cstr
from webnotes.model.controller import DocListController from webnotes.model.controller import DocListController
class InvalidWarehouseCompany(Exception): pass class InvalidWarehouseCompany(ValidationError): pass
class SerialNoNotRequiredError(ValidationError): pass
class SerialNoRequiredError(ValidationError): pass
class SerialNoQtyError(ValidationError): pass
class SerialNoItemError(ValidationError): pass
class SerialNoWarehouseError(ValidationError): pass
class SerialNoStatusError(ValidationError): pass
class SerialNoNotExistsError(ValidationError): pass
def get_serial_nos(serial_no):
return [s.strip() for s in cstr(serial_no).strip().replace(',', '\n').split('\n') if s.strip()]
class DocType(DocListController): class DocType(DocListController):
def __init__(self, doc, doclist=[]): def __init__(self, doc, doclist=[]):
@ -15,9 +25,14 @@ class DocType(DocListController):
self.doclist = doclist self.doclist = doclist
def validate(self): def validate(self):
from stock.utils import validate_warehouse_user
if not hasattr(webnotes, "new_stock_ledger_entries"):
webnotes.new_stock_ledger_entries = []
webnotes.new_stock_ledger_entries.append(self.doc)
self.validate_mandatory() self.validate_mandatory()
self.validate_item() self.validate_item()
self.validate_warehouse_user() validate_warehouse_user(self.doc.warehouse)
self.validate_warehouse_company() self.validate_warehouse_company()
self.actual_amt_check() self.actual_amt_check()
self.check_stock_frozen_date() self.check_stock_frozen_date()
@ -39,16 +54,6 @@ class DocType(DocListController):
self.doc.fields.pop('batch_bal') self.doc.fields.pop('batch_bal')
def validate_warehouse_user(self):
if webnotes.session.user=="Administrator":
return
warehouse_users = [p[0] for p in webnotes.conn.sql("""select user from `tabWarehouse User`
where parent=%s""", self.doc.warehouse)]
if warehouse_users and not webnotes.session.user in warehouse_users:
webnotes.msgprint(_("User not allowed entry in the Warehouse") \
+ ": " + webnotes.session.user + " / " + self.doc.warehouse, raise_exception = 1)
def validate_warehouse_company(self): def validate_warehouse_company(self):
warehouse_company = webnotes.conn.get_value("Warehouse", self.doc.warehouse, "company") warehouse_company = webnotes.conn.get_value("Warehouse", self.doc.warehouse, "company")
if warehouse_company and warehouse_company != self.doc.company: if warehouse_company and warehouse_company != self.doc.company:
@ -59,7 +64,7 @@ class DocType(DocListController):
def validate_mandatory(self): def validate_mandatory(self):
mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company'] mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company']
for k in mandatory: for k in mandatory:
if self.doc.fields.get(k)==None: if not self.doc.fields.get(k):
msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1) msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1)
elif k == 'warehouse': elif k == 'warehouse':
if not webnotes.conn.sql("select name from tabWarehouse where name = '%s'" % self.doc.fields.get(k)): if not webnotes.conn.sql("select name from tabWarehouse where name = '%s'" % self.doc.fields.get(k)):
@ -67,35 +72,105 @@ class DocType(DocListController):
def validate_item(self): def validate_item(self):
item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus, item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus,
ifnull(is_stock_item, 'No') from tabItem where name=%s""", is_stock_item, has_serial_no, serial_no_series
self.doc.item_code) from tabItem where name=%s""",
self.doc.item_code, as_dict=True)[0]
# check item exists if item_det.is_stock_item != 'Yes':
if item_det: webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code)
item_det = item_det and item_det[0]
else:
msgprint("Item: '%s' does not exist in the system. Please check." % self.doc.item_code, raise_exception = 1)
if item_det[3]!='Yes':
webnotes.msgprint("""Item: "%s" is not a Stock Item.""" % self.doc.item_code,
raise_exception=1)
# check if item is trashed
if cint(item_det[2])==2:
msgprint("Item: '%s' is trashed, cannot make a stock transaction against a trashed item" % self.doc.item_code, raise_exception = 1)
# check if batch number is required # check if batch number is required
if item_det[1]=='Yes' and self.doc.voucher_type != 'Stock Reconciliation': if item_det.has_batch_no =='Yes' and self.doc.voucher_type != 'Stock Reconciliation':
if not self.doc.batch_no: if not self.doc.batch_no:
msgprint("Batch number is mandatory for Item '%s'" % self.doc.item_code, raise_exception = 1) webnotes.throw("Batch number is mandatory for Item '%s'" % self.doc.item_code)
raise Exception
# check if batch belongs to item # check if batch belongs to item
if not webnotes.conn.sql("select name from `tabBatch` where item='%s' and name ='%s' and docstatus != 2" % (self.doc.item_code, self.doc.batch_no)): if not webnotes.conn.sql("""select name from `tabBatch`
msgprint("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code), raise_exception = 1) where item='%s' and name ='%s' and docstatus != 2""" % (self.doc.item_code, self.doc.batch_no)):
webnotes.throw("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code))
self.validate_serial_no(item_det)
def validate_serial_no(self, item_det):
if item_det.has_serial_no=="No":
if self.doc.serial_no:
webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " + self.doc.item),
SerialNoNotRequiredError)
else:
if self.doc.serial_no:
serial_nos = get_serial_nos(self.doc.serial_no)
if cint(self.doc.actual_qty) != flt(self.doc.actual_qty):
webnotes.throw(_("Serial No qty cannot be a fraction") + \
(": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)))
if len(serial_nos) and len(serial_nos) != abs(cint(self.doc.actual_qty)):
webnotes.throw(_("Serial Nos do not match with qty") + \
(": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)), SerialNoQtyError)
# check serial no exists, if yes then source
for serial_no in serial_nos:
if webnotes.conn.exists("Serial No", serial_no):
sr = webnotes.bean("Serial No", serial_no)
if sr.doc.item_code!=self.doc.item_code:
webnotes.throw(_("Serial No does not belong to Item") + \
(": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoItemError)
sr.make_controller().via_stock_ledger = True
if self.doc.actual_qty < 0:
if sr.doc.warehouse!=self.doc.warehouse:
webnotes.throw(_("Warehouse does not belong to Item") + \
(": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoWarehouseError)
if self.doc.voucher_type in ("Delivery Note", "Sales Invoice") \
and sr.doc.status != "Available":
webnotes.throw(_("Serial No status must be 'Available' to Deliver") + \
": " + serial_no, SerialNoStatusError)
sr.doc.warehouse = None
sr.save()
else:
sr.doc.warehouse = self.doc.warehouse
sr.save()
else:
if self.doc.actual_qty < 0:
# transfer out
webnotes.throw(_("Serial No must exist to transfer out.") + \
": " + serial_no, SerialNoNotExistsError)
else:
# transfer in
self.make_serial_no(serial_no)
else:
if item_det.serial_no_series:
from webnotes.model.doc import make_autoname
serial_nos = []
for i in xrange(cint(self.doc.actual_qty)):
serial_nos.append(self.make_serial_no(make_autoname(item_det.serial_no_series)))
self.doc.serial_no = "\n".join(serial_nos)
else:
webnotes.throw(_("Serial Number Required for Serialized Item" + ": " + self.doc.item),
SerialNoRequiredError)
def make_serial_no(self, serial_no):
sr = webnotes.new_bean("Serial No")
sr.doc.serial_no = serial_no
sr.doc.item_code = self.doc.item_code
sr.doc.purchase_rate = self.doc.incoming_rate
sr.doc.purchase_document_type = self.doc.voucher_type
sr.doc.purchase_document_no = self.doc.voucher_no
sr.doc.purchase_date = self.doc.posting_date
sr.doc.purchase_time = self.doc.posting_time
sr.make_controller().via_stock_ledger = True
sr.insert()
# set warehouse
sr.doc.warehouse = self.doc.warehouse
sr.doc.status = "Available"
sr.save()
webnotes.msgprint(_("Serial No created") + ": " + sr.doc.name)
return sr.doc.name
# Nobody can do SL Entries where posting date is before freezing date except authorized person
#----------------------------------------------------------------------------------------------
def check_stock_frozen_date(self): def check_stock_frozen_date(self):
stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or '' stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
if stock_frozen_upto: if stock_frozen_upto:
@ -107,6 +182,21 @@ class DocType(DocListController):
if not self.doc.posting_time or self.doc.posting_time == '00:0': if not self.doc.posting_time or self.doc.posting_time == '00:0':
self.doc.posting_time = '00:00' self.doc.posting_time = '00:00'
def update_serial_nos_after_submit(controller, parenttype, parentfield):
if not hasattr(webnotes, "new_stock_ledger_entries"):
return
for d in controller.doclist.get({"parentfield": parentfield}):
serial_no = None
for sle in webnotes.new_stock_ledger_entries:
if sle.voucher_detail_no==d.name:
serial_no = sle.serial_no
break
if d.serial_no != serial_no:
d.serial_no = serial_no
webnotes.conn.set_value(d.doctype, d.name, "serial_no", serial_no)
def on_doctype_update(): def on_doctype_update():
if not webnotes.conn.sql("""show index from `tabStock Ledger Entry` if not webnotes.conn.sql("""show index from `tabStock Ledger Entry`
where Key_name="posting_sort_index" """): where Key_name="posting_sort_index" """):

View File

@ -293,7 +293,7 @@ class DocType(StockController):
self.doc.stock_value_difference = 0.0 self.doc.stock_value_difference = 0.0
for d in self.entries: for d in self.entries:
self.doc.stock_value_difference -= get_buying_amount(d.item_code, self.doc.doctype, self.doc.name, self.doc.stock_value_difference -= get_buying_amount(self.doc.doctype, self.doc.name,
d.voucher_detail_no, stock_ledger_entries.get((d.item_code, d.warehouse), [])) d.voucher_detail_no, stock_ledger_entries.get((d.item_code, d.warehouse), []))
webnotes.conn.set(self.doc, "stock_value_difference", self.doc.stock_value_difference) webnotes.conn.set(self.doc, "stock_value_difference", self.doc.stock_value_difference)

View File

@ -16,5 +16,9 @@ test_records = [
"doctype": "Warehouse", "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse 2", "warehouse_name": "_Test Warehouse 2",
"company": "_Test Company 1" "company": "_Test Company 1"
}, {
"doctype": "Warehouse User",
"parentfield": "warehouse_users",
"user": "test2@example.com"
}] }]
] ]

View File

@ -176,7 +176,8 @@ erpnext.StockAgeing = erpnext.StockGridReport.extend({
xaxis: { xaxis: {
ticks: $.map(me.data, function(item, idx) { return [[idx+1, item.name]] }), ticks: $.map(me.data, function(item, idx) { return [[idx+1, item.name]] }),
max: 20 max: 20
} },
series: { downsample: { threshold: 1000 } }
} }
} }
}); });

View File

@ -180,6 +180,11 @@ wn.module_page["Stock"] = [
route: "query-report/Purchase Order Items To Be Received", route: "query-report/Purchase Order Items To Be Received",
doctype: "Purchase Receipt" doctype: "Purchase Receipt"
}, },
{
"label":wn._("Item Shortage Report"),
route: "Report/Bin/Item Shortage Report",
doctype: "Purchase Receipt"
},
{ {
"label":wn._("Serial No Service Contract Expiry"), "label":wn._("Serial No Service Contract Expiry"),
route: "Report/Serial No/Serial No Service Contract Expiry", route: "Report/Serial No/Serial No Service Contract Expiry",

View File

@ -235,6 +235,7 @@ erpnext.StockLedger = erpnext.StockGridReport.extend({
min: dateutil.str_to_obj(this.from_date).getTime(), min: dateutil.str_to_obj(this.from_date).getTime(),
max: dateutil.str_to_obj(this.to_date).getTime(), max: dateutil.str_to_obj(this.to_date).getTime(),
}, },
series: { downsample: { threshold: 1000 } }
} }
}, },
get_tooltip_text: function(label, x, y) { get_tooltip_text: function(label, x, y) {

View File

@ -0,0 +1,22 @@
[
{
"creation": "2013-08-20 13:43:30",
"docstatus": 0,
"modified": "2013-08-20 13:46:15",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Report",
"is_standard": "Yes",
"json": "{\"filters\":[[\"Bin\",\"projected_qty\",\"<\",\"0\"]],\"columns\":[[\"warehouse\",\"Bin\"],[\"item_code\",\"Bin\"],[\"actual_qty\",\"Bin\"],[\"ordered_qty\",\"Bin\"],[\"planned_qty\",\"Bin\"],[\"reserved_qty\",\"Bin\"],[\"projected_qty\",\"Bin\"]],\"sort_by\":\"Bin.projected_qty\",\"sort_order\":\"asc\",\"sort_by_next\":\"\",\"sort_order_next\":\"desc\"}",
"name": "__common__",
"ref_doctype": "Bin",
"report_name": "Item Shortage Report",
"report_type": "Report Builder"
},
{
"doctype": "Report",
"name": "Item Shortage Report"
}
]

View File

@ -0,0 +1,22 @@
[
{
"creation": "2013-08-20 15:08:10",
"docstatus": 0,
"modified": "2013-08-20 15:10:45",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Report",
"is_standard": "Yes",
"name": "__common__",
"query": "SELECT\n tabBin.item_code as \"Item:Link/Item:120\",\n tabBin.warehouse as \"Warehouse:Link/Warehouse:120\",\n tabBin.actual_qty as \"Actual:Float:90\",\n tabBin.indented_qty as \"Requested:Float:90\",\n tabBin.reserved_qty as \"Reserved:Float:90\",\n tabBin.ordered_qty as \"Ordered:Float:90\",\n tabBin.projected_qty as \"Projected:Float:90\"\nFROM\n tabBin, tabItem\nWHERE\n tabBin.item_code = tabItem.name\n AND tabItem.is_purchase_item = \"Yes\"\n AND tabBin.projected_qty < 0\nORDER BY\n tabBin.projected_qty ASC",
"ref_doctype": "Item",
"report_name": "Items To Be Requested",
"report_type": "Query Report"
},
{
"doctype": "Report",
"name": "Items To Be Requested"
}
]

View File

@ -8,6 +8,8 @@ from webnotes.utils import flt, cstr, nowdate, add_days, cint
from webnotes.defaults import get_global_default from webnotes.defaults import get_global_default
from webnotes.utils.email_lib import sendmail from webnotes.utils.email_lib import sendmail
class UserNotAllowedForWarehouse(webnotes.ValidationError): pass
def validate_end_of_life(item_code, end_of_life=None, verbose=1): def validate_end_of_life(item_code, end_of_life=None, verbose=1):
if not end_of_life: if not end_of_life:
end_of_life = webnotes.conn.get_value("Item", item_code, "end_of_life") end_of_life = webnotes.conn.get_value("Item", item_code, "end_of_life")
@ -152,20 +154,28 @@ def get_warehouse_list(doctype, txt, searchfield, start, page_len, filters):
wlist.append([w]) wlist.append([w])
return wlist return wlist
def get_buying_amount(item_code, voucher_type, voucher_no, voucher_detail_no, def validate_warehouse_user(warehouse):
stock_ledger_entries, item_sales_bom=None): if webnotes.session.user=="Administrator":
if item_sales_bom and item_sales_bom.get(item_code): return
warehouse_users = [p[0] for p in webnotes.conn.sql("""select user from `tabWarehouse User`
where parent=%s""", warehouse)]
if warehouse_users and not (webnotes.session.user in warehouse_users):
webnotes.throw(_("Not allowed entry in Warehouse") \
+ ": " + warehouse, UserNotAllowedForWarehouse)
def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no,
stock_ledger_entries, item_sales_bom):
# sales bom item # sales bom item
buying_amount = 0.0 buying_amount = 0.0
for bom_item in item_sales_bom[item_code]: for bom_item in item_sales_bom[item_code]:
if bom_item.get("parent_detail_docname")==voucher_detail_no: if bom_item.get("parent_detail_docname")==voucher_detail_no:
buying_amount += _get_buying_amount(voucher_type, voucher_no, voucher_detail_no, stock_ledger_entries) buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no,
return buying_amount stock_ledger_entries.get((bom_item.item_code, warehouse), []))
else:
# doesn't have sales bom
return _get_buying_amount(voucher_type, voucher_no, voucher_detail_no, stock_ledger_entries)
def _get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries): return buying_amount
def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries):
# IMP NOTE # IMP NOTE
# stock_ledger_entries should already be filtered by item_code and warehouse and # stock_ledger_entries should already be filtered by item_code and warehouse and
# sorted by posting_date desc, posting_time desc # sorted by posting_date desc, posting_time desc
@ -193,8 +203,7 @@ def reorder_item():
and exists (select name from `tabItem` and exists (select name from `tabItem`
where `tabItem`.name = `tabBin`.item_code and where `tabItem`.name = `tabBin`.item_code and
is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and
(ifnull(end_of_life, '')='') or end_of_life > now())""", (ifnull(end_of_life, '')='' or end_of_life > now()))""", as_dict=True)
as_dict=True)
for bin in bin_list: for bin in bin_list:
#check if re-order is required #check if re-order is required
item_reorder = webnotes.conn.get("Item Reorder", item_reorder = webnotes.conn.get("Item Reorder",

View File

@ -193,10 +193,6 @@ class DocType(TransactionBase):
if not chk1: if not chk1:
msgprint("Serial no "+x+" does not exist in system.") msgprint("Serial no "+x+" does not exist in system.")
raise Exception raise Exception
else:
if status=='In Store' or status=='Note in Use' or status=='Scrapped':
msgprint("Serial no "+x+" is '"+status+"'")
raise Exception
def validate(self): def validate(self):
self.validate_maintenance_detail() self.validate_maintenance_detail()

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-02-01 10:36:25", "creation": "2013-02-01 10:36:25",
"docstatus": 0, "docstatus": 0,
"modified": "2013-08-08 14:22:34", "modified": "2013-08-28 18:29:06",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -224,17 +224,6 @@
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"read_only": 1 "read_only": 1
}, },
{
"depends_on": "eval:!doc.__islocal",
"doctype": "DocField",
"fieldname": "resolution_details",
"fieldtype": "Small Text",
"label": "Resolution Details",
"no_copy": 1,
"oldfieldname": "resolution_details",
"oldfieldtype": "Text",
"read_only": 1
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "first_responded_on", "fieldname": "first_responded_on",
@ -254,6 +243,17 @@
"read_only": 1, "read_only": 1,
"search_index": 0 "search_index": 0
}, },
{
"depends_on": "eval:!doc.__islocal",
"doctype": "DocField",
"fieldname": "resolution_details",
"fieldtype": "Small Text",
"label": "Resolution Details",
"no_copy": 1,
"oldfieldname": "resolution_details",
"oldfieldtype": "Text",
"read_only": 0
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "content_type", "fieldname": "content_type",

3488
translations/it.csv Normal file

File diff suppressed because it is too large Load Diff

View File

View File

@ -0,0 +1,3 @@
body, #container, .outer {
background-color: #888 !important;
}

View File

@ -0,0 +1,25 @@
<div class="container">
<div class="row" style="margin-top: 100px;">
<div class="col-sm-offset-3 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
Start ERPNext Demo
</div>
<div class="panel-body">
<p>
<input id="lead-email" type="email"
class="form-control" placeholder="Your Email Id (optional)">
</p>
<p>
<button type="submit" id="login_btn"
class="btn btn-primary btn-large">Launch Demo</button>
</p>
<hr>
<p class="text-muted small">Some functionality is disabled for the demo app. The demo data will be cleared regulary. To start your own ERPNext Trial, <a href="https://erpnext.com/pricing-and-signup">click here</a></p>
</div>
</div>
</div>
</div>
<div class="row">
</div>
</div>

View File

@ -0,0 +1,27 @@
$(document).ready(function() {
$(".navbar, footer, .banner, #user-tools").toggle(false);
$("#login_btn").click(function() {
var me = this;
$(this).html("Logging In...").attr("disabled", "disabled");
wn.call({
"method": "login",
args: {
usr: "demo@erpnext.com",
pwd: "demo",
lead_email: $("#lead-email").val(),
},
callback: function(r) {
$(me).attr("disabled", false);
if(r.exc) {
alert("Error, please contact support@erpnext.com");
} else {
console.log("Logged In");
window.location.href = "app.html";
}
}
})
return false;
})
.attr("disabled", false);
})

View File

@ -0,0 +1,13 @@
def on_login(self):
from webnotes.utils import validate_email_add
import conf
if hasattr(conf, "demo_notify_url"):
if webnotes.form_dict.lead_email and validate_email_add(webnotes.form_dict.lead_email):
import requests
response = requests.post(conf.demo_notify_url, data={
"cmd":"website.helpers.contact.send_message",
"subject":"Logged into Demo",
"sender": webnotes.form_dict.lead_email,
"message": "via demo.erpnext.com"
})

Some files were not shown because too many files have changed in this diff Show More