Merge branch 'develop'

This commit is contained in:
Anand Doshi
2015-08-13 15:44:03 +05:30
73 changed files with 4240 additions and 1331 deletions

View File

@@ -16,7 +16,7 @@ ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a
### Full Install ### Full Install
The Easy Way install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt). New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
@@ -71,6 +71,6 @@ We do not allow the use of the trademark in advertising, including AdSense/AdWor
Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them. Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them.
When in doubt about your use of the ERPNext name or logo, please contact the Frappe Technologies for clarification. When in doubt about your use of the ERPNext name or logo, please contact Frappe Technologies for clarification.
(inspired from WordPress) (inspired by WordPress)

View File

@@ -1,2 +1,2 @@
from __future__ import unicode_literals from __future__ import unicode_literals
__version__ = '5.5.1' __version__ = '5.6.0'

View File

@@ -19,7 +19,8 @@ class BankReconciliation(Document):
dl = frappe.db.sql("""select t1.name, t1.cheque_no, t1.cheque_date, t2.debit, dl = frappe.db.sql("""select t1.name, t1.cheque_no, t1.cheque_date, t2.debit,
t2.credit, t1.posting_date, t2.against_account, t1.clearance_date t2.credit, t1.posting_date, t2.against_account, t1.clearance_date,
t2.reference_type, t2.reference_name
from from
`tabJournal Entry` t1, `tabJournal Entry Account` t2 `tabJournal Entry` t1, `tabJournal Entry Account` t2
where where

View File

@@ -1,11 +1,19 @@
{ {
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2013-02-22 01:27:37", "creation": "2013-02-22 01:27:37",
"custom": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"fieldname": "voucher_id", "fieldname": "voucher_id",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Voucher ID", "label": "Voucher ID",
"no_copy": 0, "no_copy": 0,
@@ -13,46 +21,84 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Journal Entry", "options": "Journal Entry",
"permlevel": 0, "permlevel": 0,
"search_index": 0 "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "clearance_date", "fieldname": "clearance_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Clearance Date", "label": "Clearance Date",
"no_copy": 0, "no_copy": 0,
"oldfieldname": "clearance_date", "oldfieldname": "clearance_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
"permlevel": 0, "permlevel": 0,
"search_index": 0 "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "against_account", "fieldname": "against_account",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Against Account", "label": "Against Account",
"no_copy": 0, "no_copy": 0,
"oldfieldname": "against_account", "oldfieldname": "against_account",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 1, "read_only": 1,
"search_index": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "cheque_number", "fieldname": "cheque_number",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Cheque Number", "label": "Cheque Number",
"no_copy": 0, "no_copy": 0,
"oldfieldname": "cheque_number", "oldfieldname": "cheque_number",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 1, "read_only": 1,
"search_index": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "debit", "fieldname": "debit",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Debit", "label": "Debit",
"no_copy": 0, "no_copy": 0,
@@ -60,12 +106,21 @@
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 1, "read_only": 1,
"search_index": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "credit", "fieldname": "credit",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Credit", "label": "Credit",
"no_copy": 0, "no_copy": 0,
@@ -73,40 +128,113 @@
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 1, "read_only": 1,
"search_index": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "reference_type",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference Type",
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference Name",
"no_copy": 0,
"options": "reference_type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "posting_date", "fieldname": "posting_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Posting Date", "label": "Posting Date",
"no_copy": 0, "no_copy": 0,
"oldfieldname": "posting_date", "oldfieldname": "posting_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 1, "read_only": 1,
"search_index": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "cheque_date", "fieldname": "cheque_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Cheque Date", "label": "Cheque Date",
"no_copy": 0, "no_copy": 0,
"oldfieldname": "cheque_date", "oldfieldname": "cheque_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 1, "read_only": 1,
"search_index": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1, "idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"modified": "2015-04-21 01:29:29.570890", "modified": "2015-08-10 16:59:43.974705",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Reconciliation Detail", "name": "Bank Reconciliation Detail",
"owner": "Administrator", "owner": "Administrator",
"permissions": [] "permissions": [],
"read_only": 0,
"read_only_onload": 0
} }

View File

@@ -50,32 +50,51 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
$.each([["against_voucher", "Purchase Invoice", "supplier"], $.each([["against_voucher", "Purchase Invoice", "supplier"],
["against_invoice", "Sales Invoice", "customer"]], function(i, opts) { ["against_invoice", "Sales Invoice", "customer"]], function(i, opts) {
me.frm.set_query(opts[0], "accounts", function(doc, cdt, cdn) {
var jvd = frappe.get_doc(cdt, cdn);
frappe.model.validate_missing(jvd, "party_type");
frappe.model.validate_missing(jvd, "party");
return {
filters: [
[opts[1], opts[2], "=", jvd.party],
[opts[1], "docstatus", "=", 1],
[opts[1], "outstanding_amount", "!=", 0]
]
};
});
}); });
this.frm.set_query("against_jv", "accounts", function(doc, cdt, cdn) { me.frm.set_query("reference_name", "accounts", function(doc, cdt, cdn) {
var jvd = frappe.get_doc(cdt, cdn); var jvd = frappe.get_doc(cdt, cdn);
frappe.model.validate_missing(jvd, "account");
return { // expense claim
query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_jv", if(jvd.reference_type==="Expense Claim") {
filters: { return {};
account: jvd.account, }
party: jvd.party
} // journal entry
if(jvd.reference_type==="Journal Entry") {
frappe.model.validate_missing(jvd, "account");
return {
query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_jv",
filters: {
account: jvd.account,
party: jvd.party
}
};
}
// against party
frappe.model.validate_missing(jvd, "party_type");
frappe.model.validate_missing(jvd, "party");
var out = {
filters: [
[jvd.reference_type, jvd.reference_type.indexOf("Sales")===0 ? "customer" : "supplier", "=", jvd.party],
[jvd.reference_type, "docstatus", "=", 1],
]
}; };
if(in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
} else {
out.filters.push([jvd.reference_type, "per_billed", "<", 100]);
}
return out;
}); });
}, },
setup_balance_formatter: function() { setup_balance_formatter: function() {
@@ -93,24 +112,16 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
}) })
}, },
against_voucher: function(doc, cdt, cdn) { reference_name: function(doc, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn); var d = frappe.get_doc(cdt, cdn);
if (d.against_voucher && !flt(d.debit)) { if (d.reference_type==="Purchase Invoice" && !flt(d.debit)) {
this.get_outstanding('Purchase Invoice', d.against_voucher, d); this.get_outstanding('Purchase Invoice', d.reference_name, d);
} }
}, if (d.reference_type==="Sales Invoice" && !flt(d.credit)) {
this.get_outstanding('Sales Invoice', d.reference_name, d);
against_invoice: function(doc, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn);
if (d.against_invoice && !flt(d.credit)) {
this.get_outstanding('Sales Invoice', d.against_invoice, d);
} }
}, if (d.reference_type==="Journal Entry" && !flt(d.credit) && !flt(d.debit)) {
this.get_outstanding('Journal Entry', d.reference_name, d);
against_jv: function(doc, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn);
if (d.against_jv && !flt(d.credit) && !flt(d.debit)) {
this.get_outstanding('Journal Entry', d.against_jv, d);
} }
}, },
@@ -214,11 +225,12 @@ cur_frm.cscript.account = function(doc,dt,dn) {
var d = locals[dt][dn]; var d = locals[dt][dn];
if(d.account) { if(d.account) {
return frappe.call({ return frappe.call({
method: "erpnext.accounts.utils.get_balance_on", method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
args: {account: d.account, date: doc.posting_date}, args: {account: d.account, date: doc.posting_date},
callback: function(r) { callback: function(r) {
d.balance = r.message; $.extend(d, r.message);
refresh_field('balance', d.name, 'accounts'); refresh_field('balance', d.name, 'accounts');
refresh_field('party_type', d.name, 'accounts');
} }
}); });
} }

View File

@@ -28,13 +28,10 @@ class JournalEntry(AccountsController):
self.validate_entries_for_advance() self.validate_entries_for_advance()
self.validate_debit_and_credit() self.validate_debit_and_credit()
self.validate_against_jv() self.validate_against_jv()
self.validate_against_sales_invoice() self.validate_reference_doc()
self.validate_against_purchase_invoice()
self.set_against_account() self.set_against_account()
self.create_remarks() self.create_remarks()
self.set_print_format_fields() self.set_print_format_fields()
self.validate_against_sales_order()
self.validate_against_purchase_order()
self.check_due_date() self.check_due_date()
self.validate_expense_claim() self.validate_expense_claim()
self.validate_credit_debit_note() self.validate_credit_debit_note()
@@ -54,10 +51,8 @@ class JournalEntry(AccountsController):
advance_paid = frappe._dict() advance_paid = frappe._dict()
for d in self.get("accounts"): for d in self.get("accounts"):
if d.is_advance: if d.is_advance:
if d.against_sales_order: if d.reference_type in ("Sales Order", "Purchase Order"):
advance_paid.setdefault("Sales Order", []).append(d.against_sales_order) advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
elif d.against_purchase_order:
advance_paid.setdefault("Purchase Order", []).append(d.against_purchase_order)
for voucher_type, order_list in advance_paid.items(): for voucher_type, order_list in advance_paid.items():
for voucher_no in list(set(order_list)): for voucher_no in list(set(order_list)):
@@ -65,7 +60,7 @@ class JournalEntry(AccountsController):
def on_cancel(self): def on_cancel(self):
from erpnext.accounts.utils import remove_against_link_from_jv from erpnext.accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doctype, self.name, "against_jv") remove_against_link_from_jv(self.doctype, self.name)
self.make_gl_entries(1) self.make_gl_entries(1)
self.update_advance_paid() self.update_advance_paid()
@@ -93,10 +88,8 @@ class JournalEntry(AccountsController):
for d in self.get("accounts"): for d in self.get("accounts"):
if d.party_type and d.party and d.get("credit" if d.party_type=="Customer" else "debit") > 0: if d.party_type and d.party and d.get("credit" if d.party_type=="Customer" else "debit") > 0:
due_date = None due_date = None
if d.against_invoice: if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
due_date = frappe.db.get_value("Sales Invoice", d.against_invoice, "due_date") due_date = frappe.db.get_value(d.reference_type, d.reference_name, "due_date")
elif d.against_voucher:
due_date = frappe.db.get_value("Purchase Invoice", d.against_voucher, "due_date")
if due_date and getdate(self.cheque_date) > getdate(due_date): if due_date and getdate(self.cheque_date) > getdate(due_date):
diff = date_diff(self.cheque_date, due_date) diff = date_diff(self.cheque_date, due_date)
@@ -115,17 +108,17 @@ class JournalEntry(AccountsController):
def validate_entries_for_advance(self): def validate_entries_for_advance(self):
for d in self.get('accounts'): for d in self.get('accounts'):
if not (d.against_voucher and d.against_invoice and d.against_jv): if d.reference_type not in ("Sales Invoice", "Purchase Invoice", "Journal Entry"):
if (d.party_type == 'Customer' and flt(d.credit) > 0) or \ if (d.party_type == 'Customer' and flt(d.credit) > 0) or \
(d.party_type == 'Supplier' and flt(d.debit) > 0): (d.party_type == 'Supplier' and flt(d.debit) > 0):
if not d.is_advance: if d.is_advance=="No":
msgprint(_("Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.").format(d.idx, d.account)) msgprint(_("Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.").format(d.idx, d.account))
elif (d.against_sales_order or d.against_purchase_order) and d.is_advance != "Yes": elif d.reference_type in ("Sales Order", "Purchase Order") and d.is_advance != "Yes":
frappe.throw(_("Row {0}: Payment against Sales/Purchase Order should always be marked as advance").format(d.idx)) frappe.throw(_("Row {0}: Payment against Sales/Purchase Order should always be marked as advance").format(d.idx))
def validate_against_jv(self): def validate_against_jv(self):
for d in self.get('accounts'): for d in self.get('accounts'):
if d.against_jv: if d.reference_type=="Journal Entry":
account_root_type = frappe.db.get_value("Account", d.account, "root_type") account_root_type = frappe.db.get_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0: if account_root_type == "Asset" and flt(d.debit) > 0:
frappe.throw(_("For {0}, only credit accounts can be linked against another debit entry") frappe.throw(_("For {0}, only credit accounts can be linked against another debit entry")
@@ -134,17 +127,17 @@ class JournalEntry(AccountsController):
frappe.throw(_("For {0}, only debit accounts can be linked against another credit entry") frappe.throw(_("For {0}, only debit accounts can be linked against another credit entry")
.format(d.account)) .format(d.account))
if d.against_jv == self.name: if d.reference_name == self.name:
frappe.throw(_("You can not enter current voucher in 'Against Journal Entry' column")) frappe.throw(_("You can not enter current voucher in 'Against Journal Entry' column"))
against_entries = frappe.db.sql("""select * from `tabJournal Entry Account` against_entries = frappe.db.sql("""select * from `tabJournal Entry Account`
where account = %s and docstatus = 1 and parent = %s where account = %s and docstatus = 1 and parent = %s
and ifnull(against_jv, '') = '' and ifnull(against_invoice, '') = '' and ifnull(reference_type, '') in ("", "Sales Order", "Purchase Order")
and ifnull(against_voucher, '') = ''""", (d.account, d.against_jv), as_dict=True) """, (d.account, d.reference_name), as_dict=True)
if not against_entries: if not against_entries:
frappe.throw(_("Journal Entry {0} does not have account {1} or already matched against other voucher") frappe.throw(_("Journal Entry {0} does not have account {1} or already matched against other voucher")
.format(d.against_jv, d.account)) .format(d.reference_name, d.account))
else: else:
dr_or_cr = "debit" if d.credit > 0 else "credit" dr_or_cr = "debit" if d.credit > 0 else "credit"
valid = False valid = False
@@ -153,88 +146,99 @@ class JournalEntry(AccountsController):
valid = True valid = True
if not valid: if not valid:
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry") frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
.format(d.against_jv, dr_or_cr)) .format(d.reference_name, dr_or_cr))
def validate_against_sales_invoice(self): def validate_reference_doc(self):
self.validate_account_in_against_voucher("against_invoice", "Sales Invoice") """Validates reference document"""
field_dict = {
def validate_against_purchase_invoice(self): 'Sales Invoice': ["Customer", "Debit To"],
self.validate_account_in_against_voucher("against_voucher", "Purchase Invoice")
def validate_against_sales_order(self):
payment_against_voucher = self.validate_account_in_against_voucher("against_sales_order", "Sales Order")
self.validate_against_order_fields("Sales Order", payment_against_voucher)
def validate_against_purchase_order(self):
payment_against_voucher = self.validate_account_in_against_voucher("against_purchase_order", "Purchase Order")
self.validate_against_order_fields("Purchase Order", payment_against_voucher)
def validate_account_in_against_voucher(self, against_field, doctype):
payment_against_voucher = frappe._dict()
field_dict = {'Sales Invoice': ["Customer", "Debit To"],
'Purchase Invoice': ["Supplier", "Credit To"], 'Purchase Invoice': ["Supplier", "Credit To"],
'Sales Order': ["Customer"], 'Sales Order': ["Customer"],
'Purchase Order': ["Supplier"] 'Purchase Order': ["Supplier"]
} }
self.reference_totals = {}
self.reference_types = {}
for d in self.get("accounts"): for d in self.get("accounts"):
if d.get(against_field): if not d.reference_type:
dr_or_cr = "credit" if against_field in ["against_invoice", "against_sales_order"] \ d.reference_name = None
if not d.reference_name:
d.reference_type = None
if d.reference_type and d.reference_name and (d.reference_type in field_dict.keys()):
dr_or_cr = "credit" if d.reference_type in ("Sales Order", "Sales Invoice") \
else "debit" else "debit"
if against_field == "against_sales_order" and flt(d.debit) > 0:
frappe.throw(_("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, doctype))
if against_field == "against_purchase_order" and flt(d.credit) > 0: # check debit or credit type Sales / Purchase Order
frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, doctype)) if d.reference_type=="Sales Order" and flt(d.debit) > 0:
frappe.throw(_("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, d.reference_type))
against_voucher = frappe.db.get_value(doctype, d.get(against_field), if d.reference_type == "Purchase Order" and flt(d.credit) > 0:
[scrub(dt) for dt in field_dict.get(doctype)]) frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, d.reference_type))
if against_field in ["against_invoice", "against_voucher"]: # set totals
if (against_voucher[0] !=d.party or against_voucher[1] != d.account): if not d.reference_name in self.reference_totals:
frappe.throw(_("Row {0}: Party / Account does not match with \ self.reference_totals[d.reference_name] = 0.0
Customer / Debit To in {1}").format(d.idx, doctype)) self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
else: self.reference_types[d.reference_name] = d.reference_type
payment_against_voucher.setdefault(d.get(against_field), []).append(flt(d.get(dr_or_cr)))
if against_field in ["against_sales_order", "against_purchase_order"]: against_voucher = frappe.db.get_value(d.reference_type, d.reference_name,
[scrub(dt) for dt in field_dict.get(d.reference_type)])
# check if party and account match
if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
if (against_voucher[0] != d.party or against_voucher[1] != d.account):
frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
.format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
d.reference_type, d.reference_name))
# check if party matches for Sales / Purchase Order
if d.reference_type in ("Sales Order", "Purchase Order"):
# set totals
if against_voucher != d.party: if against_voucher != d.party:
frappe.throw(_("Row {0}: {1} {2} does not match with {3}") \ frappe.throw(_("Row {0}: {1} {2} does not match with {3}") \
.format(d.idx, d.party_type, d.party, doctype)) .format(d.idx, d.party_type, d.party, d.reference_type))
elif d.is_advance == "Yes":
payment_against_voucher.setdefault(d.get(against_field), []).append(flt(d.get(dr_or_cr)))
return payment_against_voucher self.validate_orders()
self.validate_invoices()
def validate_against_invoice_fields(self, doctype, payment_against_voucher): def validate_orders(self):
for voucher_no, payment_list in payment_against_voucher.items(): """Validate totals, stopped and docstatus for orders"""
voucher_properties = frappe.db.get_value(doctype, voucher_no, for reference_name, total in self.reference_totals.iteritems():
["docstatus", "outstanding_amount"]) reference_type = self.reference_types[reference_name]
if voucher_properties[0] != 1: if reference_type in ("Sales Order", "Purchase Order"):
frappe.throw(_("{0} {1} is not submitted").format(doctype, voucher_no)) voucher_properties = frappe.db.get_value(reference_type, reference_name,
["docstatus", "per_billed", "status", "advance_paid", "base_grand_total"])
if flt(voucher_properties[1]) < flt(sum(payment_list)): if voucher_properties[0] != 1:
frappe.throw(_("Payment against {0} {1} cannot be greater \ frappe.throw(_("{0} {1} is not submitted").format(reference_type, reference_name))
than Outstanding Amount {2}").format(doctype, voucher_no, voucher_properties[1]))
def validate_against_order_fields(self, doctype, payment_against_voucher): if flt(voucher_properties[1]) >= 100:
for voucher_no, payment_list in payment_against_voucher.items(): frappe.throw(_("{0} {1} is fully billed").format(reference_type, reference_name))
voucher_properties = frappe.db.get_value(doctype, voucher_no,
["docstatus", "per_billed", "status", "advance_paid", "base_grand_total"])
if voucher_properties[0] != 1: if cstr(voucher_properties[2]) == "Stopped":
frappe.throw(_("{0} {1} is not submitted").format(doctype, voucher_no)) frappe.throw(_("{0} {1} is stopped").format(reference_type, reference_name))
if flt(voucher_properties[1]) >= 100: if flt(voucher_properties[4]) < (flt(voucher_properties[3]) + total):
frappe.throw(_("{0} {1} is fully billed").format(doctype, voucher_no)) frappe.throw(_("Advance paid against {0} {1} cannot be greater \
than Grand Total {2}").format(reference_type, reference_name, voucher_properties[4]))
if cstr(voucher_properties[2]) == "Stopped": def validate_invoices(self):
frappe.throw(_("{0} {1} is stopped").format(doctype, voucher_no)) """Validate totals and docstatus for invoices"""
for reference_name, total in self.reference_totals.iteritems():
reference_type = self.reference_types[reference_name]
if flt(voucher_properties[4]) < flt(voucher_properties[3]) + flt(sum(payment_list)): if reference_type in ("Sales Invoice", "Purchase Invoice"):
frappe.throw(_("Advance paid against {0} {1} cannot be greater \ voucher_properties = frappe.db.get_value(reference_type, reference_name,
than Grand Total {2}").format(doctype, voucher_no, voucher_properties[3])) ["docstatus", "outstanding_amount"])
if voucher_properties[0] != 1:
frappe.throw(_("{0} {1} is not submitted").format(reference_type, reference_name))
if flt(voucher_properties[1]) < total:
frappe.throw(_("Payment against {0} {1} cannot be greater \
than Outstanding Amount {2}").format(reference_type, reference_name, voucher_properties[1]))
def set_against_account(self): def set_against_account(self):
accounts_debited, accounts_credited = [], [] accounts_debited, accounts_credited = [], []
@@ -274,25 +278,25 @@ class JournalEntry(AccountsController):
company_currency = get_company_currency(self.company) company_currency = get_company_currency(self.company)
for d in self.get('accounts'): for d in self.get('accounts'):
if d.against_invoice and d.credit: if d.reference_type=="Sales Invoice" and d.credit:
r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = company_currency), \ r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
d.against_invoice)) d.reference_name))
if d.against_sales_order and d.credit: if d.reference_type=="Sales Order" and d.credit:
r.append(_("{0} against Sales Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \ r.append(_("{0} against Sales Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
d.against_sales_order)) d.reference_name))
if d.against_voucher and d.debit: if d.reference_type == "Purchase Invoice" and d.debit:
bill_no = frappe.db.sql("""select bill_no, bill_date bill_no = frappe.db.sql("""select bill_no, bill_date
from `tabPurchase Invoice` where name=%s""", d.against_voucher) from `tabPurchase Invoice` where name=%s""", d.reference_name)
if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \ if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \
not in ['na', 'not applicable', 'none']: not in ['na', 'not applicable', 'none']:
r.append(_('{0} against Bill {1} dated {2}').format(fmt_money(flt(d.debit), currency=company_currency), bill_no[0][0], r.append(_('{0} against Bill {1} dated {2}').format(fmt_money(flt(d.debit), currency=company_currency), bill_no[0][0],
bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d')))) bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d'))))
if d.against_purchase_order and d.debit: if d.reference_type == "Purchase Order" and d.debit:
r.append(_("{0} against Purchase Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \ r.append(_("{0} against Purchase Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
d.against_purchase_order)) d.reference_name))
if self.user_remark: if self.user_remark:
r.append(_("Note: {0}").format(self.user_remark)) r.append(_("Note: {0}").format(self.user_remark))
@@ -331,13 +335,8 @@ class JournalEntry(AccountsController):
"against": d.against_account, "against": d.against_account,
"debit": flt(d.debit, self.precision("debit", "accounts")), "debit": flt(d.debit, self.precision("debit", "accounts")),
"credit": flt(d.credit, self.precision("credit", "accounts")), "credit": flt(d.credit, self.precision("credit", "accounts")),
"against_voucher_type": (("Purchase Invoice" if d.against_voucher else None) "against_voucher_type": d.reference_type,
or ("Sales Invoice" if d.against_invoice else None) "against_voucher": d.reference_name,
or ("Journal Entry" if d.against_jv else None)
or ("Sales Order" if d.against_sales_order else None)
or ("Purchase Order" if d.against_purchase_order else None)),
"against_voucher": d.against_voucher or d.against_invoice or d.against_jv
or d.against_sales_order or d.against_purchase_order,
"remarks": self.remark, "remarks": self.remark,
"cost_center": d.cost_center "cost_center": d.cost_center
}) })
@@ -384,11 +383,13 @@ class JournalEntry(AccountsController):
if self.write_off_based_on == 'Accounts Receivable': if self.write_off_based_on == 'Accounts Receivable':
jd1.party_type = "Customer" jd1.party_type = "Customer"
jd1.credit = flt(d.outstanding_amount, self.precision("credit", "accounts")) jd1.credit = flt(d.outstanding_amount, self.precision("credit", "accounts"))
jd1.against_invoice = cstr(d.name) jd1.reference_type = "Sales Invoice"
jd1.reference_name = cstr(d.name)
elif self.write_off_based_on == 'Accounts Payable': elif self.write_off_based_on == 'Accounts Payable':
jd1.party_type = "Supplier" jd1.party_type = "Supplier"
jd1.debit = flt(d.outstanding_amount, self.precision("debit", "accounts")) jd1.debit = flt(d.outstanding_amount, self.precision("debit", "accounts"))
jd1.against_voucher = cstr(d.name) jd1.reference_type = "Purchase Invoice"
jd1.reference_name = cstr(d.name)
jd2 = self.append('accounts', {}) jd2 = self.append('accounts', {})
if self.write_off_based_on == 'Accounts Receivable': if self.write_off_based_on == 'Accounts Receivable':
@@ -414,19 +415,20 @@ class JournalEntry(AccountsController):
def update_expense_claim(self): def update_expense_claim(self):
for d in self.accounts: for d in self.accounts:
if d.against_expense_claim: if d.reference_type=="Expense Claim":
amt = frappe.db.sql("""select sum(debit) as amt from `tabJournal Entry Account` amt = frappe.db.sql("""select sum(debit) as amt from `tabJournal Entry Account`
where against_expense_claim = %s and docstatus = 1""", d.against_expense_claim ,as_dict=1)[0].amt where reference_type = "Expense Claim" and
frappe.db.set_value("Expense Claim", d.against_expense_claim , "total_amount_reimbursed", amt) reference_name = %s and docstatus = 1""", d.reference_name ,as_dict=1)[0].amt
frappe.db.set_value("Expense Claim", d.reference_name , "total_amount_reimbursed", amt)
def validate_expense_claim(self): def validate_expense_claim(self):
for d in self.accounts: for d in self.accounts:
if d.against_expense_claim: if d.reference_type=="Expense Claim":
sanctioned_amount, reimbursed_amount = frappe.db.get_value("Expense Claim", sanctioned_amount, reimbursed_amount = frappe.db.get_value("Expense Claim",
d.against_expense_claim, ("total_sanctioned_amount", "total_amount_reimbursed")) d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed"))
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount) pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
if d.debit > pending_amount: if d.debit > pending_amount:
frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}".format(d.idx, d.against_expense_claim, pending_amount))) frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}".format(d.idx, d.reference_name, pending_amount)))
def validate_credit_debit_note(self): def validate_credit_debit_note(self):
if self.stock_entry: if self.stock_entry:
@@ -466,6 +468,7 @@ def get_default_bank_cash_account(company, voucher_type, mode_of_payment=None):
@frappe.whitelist() @frappe.whitelist()
def get_payment_entry_from_sales_invoice(sales_invoice): def get_payment_entry_from_sales_invoice(sales_invoice):
"""Returns new Journal Entry document as dict for given Sales Invoice"""
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
si = frappe.get_doc("Sales Invoice", sales_invoice) si = frappe.get_doc("Sales Invoice", sales_invoice)
jv = get_payment_entry(si) jv = get_payment_entry(si)
@@ -478,7 +481,8 @@ def get_payment_entry_from_sales_invoice(sales_invoice):
jv.get("accounts")[0].balance = get_balance_on(si.debit_to) jv.get("accounts")[0].balance = get_balance_on(si.debit_to)
jv.get("accounts")[0].party_balance = get_balance_on(party=si.customer, party_type="Customer") jv.get("accounts")[0].party_balance = get_balance_on(party=si.customer, party_type="Customer")
jv.get("accounts")[0].credit = si.outstanding_amount jv.get("accounts")[0].credit = si.outstanding_amount
jv.get("accounts")[0].against_invoice = si.name jv.get("accounts")[0].reference_type = si.doctype
jv.get("accounts")[0].reference_name = si.name
# debit bank # debit bank
jv.get("accounts")[1].debit = si.outstanding_amount jv.get("accounts")[1].debit = si.outstanding_amount
@@ -487,6 +491,7 @@ def get_payment_entry_from_sales_invoice(sales_invoice):
@frappe.whitelist() @frappe.whitelist()
def get_payment_entry_from_purchase_invoice(purchase_invoice): def get_payment_entry_from_purchase_invoice(purchase_invoice):
"""Returns new Journal Entry document as dict for given Purchase Invoice"""
pi = frappe.get_doc("Purchase Invoice", purchase_invoice) pi = frappe.get_doc("Purchase Invoice", purchase_invoice)
jv = get_payment_entry(pi) jv = get_payment_entry(pi)
jv.remark = 'Payment against Purchase Invoice {0}. {1}'.format(pi.name, pi.remarks) jv.remark = 'Payment against Purchase Invoice {0}. {1}'.format(pi.name, pi.remarks)
@@ -498,13 +503,78 @@ def get_payment_entry_from_purchase_invoice(purchase_invoice):
jv.get("accounts")[0].balance = get_balance_on(pi.credit_to) jv.get("accounts")[0].balance = get_balance_on(pi.credit_to)
jv.get("accounts")[0].party_balance = get_balance_on(party=pi.supplier, party_type="Supplier") jv.get("accounts")[0].party_balance = get_balance_on(party=pi.supplier, party_type="Supplier")
jv.get("accounts")[0].debit = pi.outstanding_amount jv.get("accounts")[0].debit = pi.outstanding_amount
jv.get("accounts")[0].against_voucher = pi.name jv.get("accounts")[0].reference_type = pi.doctype
jv.get("accounts")[0].reference_name = pi.name
# credit bank # credit bank
jv.get("accounts")[1].credit = pi.outstanding_amount jv.get("accounts")[1].credit = pi.outstanding_amount
return jv.as_dict() return jv.as_dict()
@frappe.whitelist()
def get_payment_entry_from_sales_order(sales_order):
"""Returns new Journal Entry document as dict for given Sales Order"""
from erpnext.accounts.utils import get_balance_on
from erpnext.accounts.party import get_party_account
so = frappe.get_doc("Sales Order", sales_order)
if flt(so.per_billed, 2) != 0.0:
frappe.throw(_("Can only make payment against unbilled Sales Order"))
jv = get_payment_entry(so)
jv.remark = 'Advance payment received against Sales Order {0}.'.format(so.name)
party_account = get_party_account(so.company, so.customer, "Customer")
amount = flt(so.base_grand_total) - flt(so.advance_paid)
# credit customer
jv.get("accounts")[0].account = party_account
jv.get("accounts")[0].party_type = "Customer"
jv.get("accounts")[0].party = so.customer
jv.get("accounts")[0].balance = get_balance_on(party_account)
jv.get("accounts")[0].party_balance = get_balance_on(party=so.customer, party_type="Customer")
jv.get("accounts")[0].credit = amount
jv.get("accounts")[0].reference_type = so.doctype
jv.get("accounts")[0].reference_name = so.name
jv.get("accounts")[0].is_advance = "Yes"
# debit bank
jv.get("accounts")[1].debit = amount
return jv.as_dict()
@frappe.whitelist()
def get_payment_entry_from_purchase_order(purchase_order):
"""Returns new Journal Entry document as dict for given Sales Order"""
from erpnext.accounts.utils import get_balance_on
from erpnext.accounts.party import get_party_account
po = frappe.get_doc("Purchase Order", purchase_order)
if flt(po.per_billed, 2) != 0.0:
frappe.throw(_("Can only make payment against unbilled Sales Order"))
jv = get_payment_entry(po)
jv.remark = 'Advance payment made against Purchase Order {0}.'.format(po.name)
party_account = get_party_account(po.company, po.supplier, "Supplier")
amount = flt(po.base_grand_total) - flt(po.advance_paid)
# credit customer
jv.get("accounts")[0].account = party_account
jv.get("accounts")[0].party_type = "Supplier"
jv.get("accounts")[0].party = po.supplier
jv.get("accounts")[0].balance = get_balance_on(party_account)
jv.get("accounts")[0].party_balance = get_balance_on(party=po.supplier, party_type="Supplier")
jv.get("accounts")[0].debit = amount
jv.get("accounts")[0].reference_type = po.doctype
jv.get("accounts")[0].reference_name = po.name
jv.get("accounts")[0].is_advance = "Yes"
# debit bank
jv.get("accounts")[1].credit = amount
return jv.as_dict()
def get_payment_entry(doc): def get_payment_entry(doc):
bank_account = get_default_bank_cash_account(doc.company, "Bank Entry") bank_account = get_default_bank_cash_account(doc.company, "Bank Entry")
@@ -535,13 +605,14 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark
from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s
and (ifnull(jv_detail.against_invoice, '') = '' and ifnull(jv_detail.against_voucher, '') = '' and (ifnull(jv_detail.reference_type, '') = ''
and ifnull(jv_detail.against_jv, '') = '' )
and jv.docstatus = 1 and jv.{0} like %s order by jv.name desc limit %s, %s""".format(searchfield), and jv.docstatus = 1 and jv.{0} like %s order by jv.name desc limit %s, %s""".format(searchfield),
(filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len)) (filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len))
@frappe.whitelist() @frappe.whitelist()
def get_outstanding(args): def get_outstanding(args):
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
args = eval(args) args = eval(args)
if args.get("doctype") == "Journal Entry": if args.get("doctype") == "Journal Entry":
condition = " and party=%(party)s" if args.get("party") else "" condition = " and party=%(party)s" if args.get("party") else ""
@@ -549,8 +620,7 @@ def get_outstanding(args):
against_jv_amount = frappe.db.sql(""" against_jv_amount = frappe.db.sql("""
select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0} from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0}
and ifnull(against_invoice, '')='' and ifnull(against_voucher, '')='' and ifnull(reference_type, '')=''""".format(condition), args)
and ifnull(against_jv, '')=''""".format(condition), args)
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0 against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
return { return {
@@ -569,6 +639,9 @@ def get_outstanding(args):
@frappe.whitelist() @frappe.whitelist()
def get_party_account_and_balance(company, party_type, party): def get_party_account_and_balance(company, party_type, party):
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
account = get_party_account(company, party, party_type) account = get_party_account(company, party, party_type)
@@ -580,3 +653,16 @@ def get_party_account_and_balance(company, party_type, party):
"balance": account_balance, "balance": account_balance,
"party_balance": party_balance "party_balance": party_balance
} }
@frappe.whitelist()
def get_account_balance_and_party_type(account, date):
"""Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
account_type = frappe.db.get_value("Account", account, "account_type")
return {
"balance": get_balance_on(account, date),
"party_type": {"Receivable":"Customer", "Payable":"Supplier"}.get(account_type, "")
}

View File

@@ -29,10 +29,6 @@ class TestJournalEntry(unittest.TestCase):
def jv_against_voucher_testcase(self, base_jv, test_voucher): def jv_against_voucher_testcase(self, base_jv, test_voucher):
dr_or_cr = "credit" if test_voucher.doctype in ["Sales Order", "Journal Entry"] else "debit" dr_or_cr = "credit" if test_voucher.doctype in ["Sales Order", "Journal Entry"] else "debit"
field_dict = {'Journal Entry': "against_jv",
'Sales Order': "against_sales_order",
'Purchase Order': "against_purchase_order"
}
test_voucher.insert() test_voucher.insert()
test_voucher.submit() test_voucher.submit()
@@ -42,21 +38,20 @@ class TestJournalEntry(unittest.TestCase):
where account = %s and docstatus = 1 and parent = %s""", where account = %s and docstatus = 1 and parent = %s""",
("_Test Receivable - _TC", test_voucher.name))) ("_Test Receivable - _TC", test_voucher.name)))
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account` self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
where %s=%s""" % (field_dict.get(test_voucher.doctype), '%s'), (test_voucher.name))) where reference_type = %s and reference_name = %s""", (test_voucher.doctype, test_voucher.name)))
base_jv.get("accounts")[0].is_advance = "Yes" if (test_voucher.doctype in ["Sales Order", "Purchase Order"]) else "No" base_jv.get("accounts")[0].is_advance = "Yes" if (test_voucher.doctype in ["Sales Order", "Purchase Order"]) else "No"
base_jv.get("accounts")[0].set(field_dict.get(test_voucher.doctype), test_voucher.name) base_jv.get("accounts")[0].set("reference_type", test_voucher.doctype)
base_jv.get("accounts")[0].set("reference_name", test_voucher.name)
base_jv.insert() base_jv.insert()
base_jv.submit() base_jv.submit()
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name) submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account` self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
where %s=%s""" % (field_dict.get(test_voucher.doctype), '%s'), (submitted_voucher.name))) where reference_type = %s and reference_name = %s and {0}=400""".format(dr_or_cr),
(submitted_voucher.doctype, submitted_voucher.name)))
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
where %s=%s and %s=400""" % (field_dict.get(submitted_voucher.doctype), '%s', dr_or_cr), (submitted_voucher.name)))
if base_jv.get("accounts")[0].is_advance == "Yes": if base_jv.get("accounts")[0].is_advance == "Yes":
self.advance_paid_testcase(base_jv, submitted_voucher, dr_or_cr) self.advance_paid_testcase(base_jv, submitted_voucher, dr_or_cr)
@@ -74,8 +69,8 @@ class TestJournalEntry(unittest.TestCase):
if test_voucher.doctype == "Journal Entry": if test_voucher.doctype == "Journal Entry":
# if test_voucher is a Journal Entry, test cancellation of test_voucher # if test_voucher is a Journal Entry, test cancellation of test_voucher
test_voucher.cancel() test_voucher.cancel()
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account` self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
where against_jv=%s""", test_voucher.name)) where reference_type='Journal Entry' and reference_name=%s""", test_voucher.name))
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]: elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher # if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher

View File

@@ -1,27 +1,44 @@
{ {
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash", "autoname": "hash",
"creation": "2013-02-22 01:27:39", "creation": "2013-02-22 01:27:39",
"custom": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"fieldname": "account", "fieldname": "account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Account", "label": "Account",
"no_copy": 0,
"oldfieldname": "account", "oldfieldname": "account",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Account", "options": "Account",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"print_width": "250px", "print_width": "250px",
"read_only": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 1, "search_index": 1,
"set_only_once": 0,
"unique": 0,
"width": "250px" "width": "250px"
}, },
{ {
"allow_on_submit": 0,
"fieldname": "balance", "fieldname": "balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Account Balance", "label": "Account Balance",
"no_copy": 1, "no_copy": 1,
@@ -30,186 +47,336 @@
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"default": ":Company", "default": ":Company",
"description": "If Income or Expense", "description": "If Income or Expense",
"fieldname": "cost_center", "fieldname": "cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Cost Center", "label": "Cost Center",
"no_copy": 0,
"oldfieldname": "cost_center", "oldfieldname": "cost_center",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Cost Center", "options": "Cost Center",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_width": "180px", "print_width": "180px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "180px" "width": "180px"
}, },
{ {
"allow_on_submit": 0,
"fieldname": "col_break1", "fieldname": "col_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "party_type", "fieldname": "party_type",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Party Type", "label": "Party Type",
"no_copy": 0,
"options": "DocType", "options": "DocType",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "party", "fieldname": "party",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Party", "label": "Party",
"no_copy": 0,
"options": "party_type", "options": "party_type",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "party_balance", "fieldname": "party_balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Party Balance", "label": "Party Balance",
"no_copy": 0,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"read_only": 1 "print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "sec_break1", "fieldname": "sec_break1",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Amount", "label": "Amount",
"permlevel": 0 "no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "debit", "fieldname": "debit",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Debit", "label": "Debit",
"no_copy": 0,
"oldfieldname": "debit", "oldfieldname": "debit",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "col_break2", "fieldname": "col_break2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "credit", "fieldname": "credit",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Credit", "label": "Credit",
"no_copy": 0,
"oldfieldname": "credit", "oldfieldname": "credit",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "reference", "fieldname": "reference",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference", "label": "Reference",
"permlevel": 0 "no_copy": 0,
},
{
"fieldname": "against_invoice",
"fieldtype": "Link",
"in_filter": 1,
"label": "Against Sales Invoice",
"no_copy": 1,
"oldfieldname": "against_invoice",
"oldfieldtype": "Link",
"options": "Sales Invoice",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"search_index": 1 "read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"fieldname": "against_voucher", "allow_on_submit": 0,
"fieldtype": "Link", "fieldname": "reference_type",
"in_filter": 1, "fieldtype": "Select",
"in_list_view": 1, "hidden": 0,
"label": "Against Purchase Invoice", "ignore_user_permissions": 0,
"no_copy": 1, "in_filter": 0,
"oldfieldname": "against_voucher", "in_list_view": 0,
"oldfieldtype": "Link", "label": "Reference Type",
"options": "Purchase Invoice", "no_copy": 0,
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"search_index": 1 "read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"fieldname": "against_jv", "allow_on_submit": 0,
"fieldtype": "Link", "fieldname": "reference_name",
"in_filter": 1, "fieldtype": "Dynamic Link",
"label": "Against Journal Entry", "hidden": 0,
"no_copy": 1, "ignore_user_permissions": 0,
"oldfieldname": "against_jv", "in_filter": 0,
"oldfieldtype": "Link", "in_list_view": 0,
"options": "Journal Entry", "label": "Reference Name",
"no_copy": 0,
"options": "reference_type",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"search_index": 1 "read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "col_break3", "fieldname": "col_break3",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
}, "ignore_user_permissions": 0,
{ "in_filter": 0,
"fieldname": "against_sales_order", "in_list_view": 0,
"fieldtype": "Link", "no_copy": 0,
"label": "Against Sales Order",
"options": "Sales Order",
"permlevel": 0
},
{
"fieldname": "against_purchase_order",
"fieldtype": "Link",
"label": "Against Purchase Order",
"options": "Purchase Order",
"permlevel": 0
},
{
"fieldname": "against_expense_claim",
"fieldtype": "Link",
"label": "Against Expense Claim",
"options": "Expense Claim",
"permlevel": 0, "permlevel": 0,
"precision": "" "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "is_advance", "fieldname": "is_advance",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Advance", "label": "Is Advance",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "is_advance", "oldfieldname": "is_advance",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "No\nYes", "options": "No\nYes",
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "against_account", "fieldname": "against_account",
"fieldtype": "Text", "fieldtype": "Text",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Against Account", "label": "Against Account",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "against_account", "oldfieldname": "against_account",
"oldfieldtype": "Text", "oldfieldtype": "Text",
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1, "idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"modified": "2015-02-19 01:07:00.388689", "modified": "2015-08-11 10:44:11.432623",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry Account", "name": "Journal Entry Account",
"owner": "Administrator", "owner": "Administrator",
"permissions": [] "permissions": [],
"read_only": 0,
"read_only_onload": 0
} }

View File

@@ -34,8 +34,8 @@ class PaymentReconciliation(Document):
t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1 t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
and t2.party_type = %(party_type)s and t2.party = %(party)s and t2.party_type = %(party_type)s and t2.party = %(party)s
and t2.account = %(account)s and {dr_or_cr} > 0 and t2.account = %(account)s and {dr_or_cr} > 0
and ifnull(t2.against_voucher, '')='' and ifnull(t2.against_invoice, '')='' and ifnull(t2.reference_type, '') in ('', 'Sales Order', 'Purchase Order')
and ifnull(t2.against_jv, '')='' {cond} {cond}
and (CASE and (CASE
WHEN t1.voucher_type in ('Debit Note', 'Credit Note') WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
THEN 1=1 THEN 1=1

View File

@@ -12,13 +12,6 @@ class PaymentTool(Document):
def make_journal_entry(self): def make_journal_entry(self):
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
total_payment_amount = 0.00 total_payment_amount = 0.00
invoice_voucher_type = {
'Sales Invoice': 'against_invoice',
'Purchase Invoice': 'against_voucher',
'Journal Entry': 'against_jv',
'Sales Order': 'against_sales_order',
'Purchase Order': 'against_purchase_order',
}
jv = frappe.new_doc('Journal Entry') jv = frappe.new_doc('Journal Entry')
jv.voucher_type = 'Journal Entry' jv.voucher_type = 'Journal Entry'
@@ -41,7 +34,8 @@ class PaymentTool(Document):
d1.party = self.party d1.party = self.party
d1.balance = get_balance_on(self.party_account) d1.balance = get_balance_on(self.party_account)
d1.set("debit" if self.received_or_paid=="Paid" else "credit", flt(v.payment_amount)) d1.set("debit" if self.received_or_paid=="Paid" else "credit", flt(v.payment_amount))
d1.set(invoice_voucher_type.get(v.against_voucher_type), v.against_voucher_no) d1.set("reference_type", v.against_voucher_type)
d1.set("reference_name", v.against_voucher_no)
d1.set('is_advance', 'Yes' if v.against_voucher_type in ['Sales Order', 'Purchase Order'] else 'No') d1.set('is_advance', 'Yes' if v.against_voucher_type in ['Sales Order', 'Purchase Order'] else 'No')
total_payment_amount = flt(total_payment_amount) + flt(d1.debit) - flt(d1.credit) total_payment_amount = flt(total_payment_amount) + flt(d1.debit) - flt(d1.credit)

View File

@@ -26,7 +26,8 @@ class TestPaymentTool(unittest.TestCase):
self.create_against_jv(jv_test_records[0], { self.create_against_jv(jv_test_records[0], {
"party": "_Test Customer 3", "party": "_Test Customer 3",
"against_sales_order": so1.name, "reference_type": "Sales Order",
"reference_name": so1.name,
"is_advance": "Yes" "is_advance": "Yes"
}) })
@@ -36,7 +37,8 @@ class TestPaymentTool(unittest.TestCase):
self.create_against_jv(jv_test_records[0], { self.create_against_jv(jv_test_records[0], {
"party": "_Test Customer 3", "party": "_Test Customer 3",
"against_sales_order": so2.name, "reference_type": "Sales Order",
"reference_name": so2.name,
"credit": 1000, "credit": 1000,
"is_advance": "Yes" "is_advance": "Yes"
}) })
@@ -52,7 +54,8 @@ class TestPaymentTool(unittest.TestCase):
self.create_against_jv(jv_test_records[0], { self.create_against_jv(jv_test_records[0], {
"party": "_Test Customer 3", "party": "_Test Customer 3",
"against_invoice": si1.name "reference_type": si1.doctype,
"reference_name": si1.name
}) })
#Create SI with no outstanding #Create SI with no outstanding
si2 = self.create_voucher(si_test_records[0], { si2 = self.create_voucher(si_test_records[0], {
@@ -62,7 +65,8 @@ class TestPaymentTool(unittest.TestCase):
self.create_against_jv(jv_test_records[0], { self.create_against_jv(jv_test_records[0], {
"party": "_Test Customer 3", "party": "_Test Customer 3",
"against_invoice": si2.name, "reference_type": si2.doctype,
"reference_name": si2.name,
"credit": 561.80 "credit": 561.80
}) })
@@ -153,29 +157,12 @@ class TestPaymentTool(unittest.TestCase):
new_jv = paytool.make_journal_entry() new_jv = paytool.make_journal_entry()
#Create a list of expected values as [party account, payment against, against_jv, against_invoice,
#against_voucher, against_sales_order, against_purchase_order]
expected_values = [
[paytool.party_account, paytool.party, 100.00, expected_outstanding.get("Journal Entry")[0], None, None, None, None],
[paytool.party_account, paytool.party, 100.00, None, expected_outstanding.get("Sales Invoice")[0], None, None, None],
[paytool.party_account, paytool.party, 100.00, None, None, expected_outstanding.get("Purchase Invoice")[0], None, None],
[paytool.party_account, paytool.party, 100.00, None, None, None, expected_outstanding.get("Sales Order")[0], None],
[paytool.party_account, paytool.party, 100.00, None, None, None, None, expected_outstanding.get("Purchase Order")[0]]
]
for jv_entry in new_jv.get("accounts"): for jv_entry in new_jv.get("accounts"):
if paytool.party_account == jv_entry.get("account") and paytool.party == jv_entry.get("party"): if paytool.party_account == jv_entry.get("account") and paytool.party == jv_entry.get("party"):
row = [ self.assertEquals(100.00,
jv_entry.get("account"), jv_entry.get("debit" if paytool.party_type=="Supplier" else "credit"))
jv_entry.get("party"), self.assertEquals(jv_entry.reference_name,
jv_entry.get("debit" if paytool.party_type=="Supplier" else "credit"), expected_outstanding[jv_entry.reference_type][0])
jv_entry.get("against_jv"),
jv_entry.get("against_invoice"),
jv_entry.get("against_voucher"),
jv_entry.get("against_sales_order"),
jv_entry.get("against_purchase_order"),
]
self.assertTrue(row in expected_values)
self.assertEquals(new_jv.get("cheque_no"), paytool.reference_no) self.assertEquals(new_jv.get("cheque_no"), paytool.reference_no)
self.assertEquals(new_jv.get("cheque_date"), paytool.reference_date) self.assertEquals(new_jv.get("cheque_date"), paytool.reference_date)

View File

@@ -11,7 +11,6 @@ from frappe.model.document import Document
class POSProfile(Document): class POSProfile(Document):
def validate(self): def validate(self):
self.check_for_duplicate() self.check_for_duplicate()
self.validate_expense_account()
self.validate_all_link_fields() self.validate_all_link_fields()
def check_for_duplicate(self): def check_for_duplicate(self):
@@ -26,11 +25,6 @@ class POSProfile(Document):
msgprint(_("Global POS Profile {0} already created for company {1}").format(res[0][0], msgprint(_("Global POS Profile {0} already created for company {1}").format(res[0][0],
self.company), raise_exception=1) self.company), raise_exception=1)
def validate_expense_account(self):
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) \
and not self.expense_account:
msgprint(_("Expense Account is mandatory"), raise_exception=1)
def validate_all_link_fields(self): def validate_all_link_fields(self):
accounts = {"Account": [self.cash_bank_account, self.income_account, accounts = {"Account": [self.cash_bank_account, self.income_account,
self.expense_account], "Cost Center": [self.cost_center], self.expense_account], "Cost Center": [self.cost_center],

File diff suppressed because it is too large Load Diff

View File

@@ -147,6 +147,7 @@ def get_pricing_rule_for_item(args):
if pricing_rule: if pricing_rule:
item_details.pricing_rule = pricing_rule.name item_details.pricing_rule = pricing_rule.name
item_details.pricing_rule_for = pricing_rule.price_or_discount
if pricing_rule.price_or_discount == "Price": if pricing_rule.price_or_discount == "Price":
item_details.update({ item_details.update({
"price_list_rate": pricing_rule.price/flt(args.conversion_rate) \ "price_list_rate": pricing_rule.price/flt(args.conversion_rate) \

View File

@@ -25,10 +25,9 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
if(!doc.is_return) { if(!doc.is_return) {
if(doc.docstatus==1) { if(doc.docstatus==1) {
if(doc.outstanding_amount > 0) { if(doc.outstanding_amount > 0) {
this.frm.add_custom_button(__('Make Payment Entry'), this.make_bank_entry); this.frm.add_custom_button(__('Payment'), this.make_bank_entry).addClass("btn-primary");
} }
cur_frm.add_custom_button(__('Debit Note'), this.make_debit_note);
cur_frm.add_custom_button(__('Make Debit Note'), this.make_debit_note);
} }
if(doc.docstatus===0) { if(doc.docstatus===0) {

View File

@@ -41,7 +41,7 @@ class PurchaseInvoice(BuyingController):
self.po_required() self.po_required()
self.pr_required() self.pr_required()
self.validate_supplier_invoice() self.validate_supplier_invoice()
self.validate_advance_jv("advances", "purchase_order") self.validate_advance_jv("Purchase Order")
self.check_active_purchase_items() self.check_active_purchase_items()
self.check_conversion_rate() self.check_conversion_rate()
@@ -365,7 +365,7 @@ class PurchaseInvoice(BuyingController):
def on_cancel(self): def on_cancel(self):
if not self.is_return: if not self.is_return:
from erpnext.accounts.utils import remove_against_link_from_jv from erpnext.accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doctype, self.name, "against_voucher") remove_against_link_from_jv(self.doctype, self.name)
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_for_zero_amount_refdoc("Purchase Order")

View File

@@ -218,17 +218,14 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.load_from_db() pi.load_from_db()
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account` self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
where against_voucher=%s""", pi.name)) where reference_type='Purchase Invoice' and reference_name=%s and debit=300""", pi.name))
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
where against_voucher=%s and debit=300""", pi.name))
self.assertEqual(pi.outstanding_amount, 1212.30) self.assertEqual(pi.outstanding_amount, 1212.30)
pi.cancel() pi.cancel()
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account` self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
where against_voucher=%s""", pi.name)) where reference_type='Purchase Invoice' and reference_name=%s""", pi.name))
def test_recurring_invoice(self): def test_recurring_invoice(self):
from erpnext.controllers.tests.test_recurring_document import test_recurring_document from erpnext.controllers.tests.test_recurring_document import test_recurring_document

View File

@@ -48,6 +48,9 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
if(doc.update_stock) this.show_stock_ledger(); if(doc.update_stock) this.show_stock_ledger();
if(doc.docstatus==1 && !doc.is_return) { if(doc.docstatus==1 && !doc.is_return) {
cur_frm.add_custom_button(doc.update_stock ? __('Sales Return') : __('Credit Note'),
this.make_sales_return);
if(cint(doc.update_stock)!=1) { if(cint(doc.update_stock)!=1) {
// show Make Delivery Note button only if Sales Invoice is not created from Delivery Note // show Make Delivery Note button only if Sales Invoice is not created from Delivery Note
var from_delivery_note = false; var from_delivery_note = false;
@@ -57,16 +60,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
}); });
if(!from_delivery_note) { if(!from_delivery_note) {
cur_frm.add_custom_button(__('Make Delivery'), cur_frm.cscript['Make Delivery Note']) cur_frm.add_custom_button(__('Delivery'), cur_frm.cscript['Make Delivery Note']).addClass("btn-primary");
} }
} }
if(doc.outstanding_amount!=0 && !cint(doc.is_return)) { if(doc.outstanding_amount!=0 && !cint(doc.is_return)) {
cur_frm.add_custom_button(__('Make Payment Entry'), cur_frm.cscript.make_bank_entry); cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry).addClass("btn-primary");
} }
cur_frm.add_custom_button(doc.update_stock ? __('Make Sales Return') : __('Make Credit Note'),
this.make_sales_return);
} }
// Show buttons only when pos view is active // Show buttons only when pos view is active

View File

@@ -46,7 +46,7 @@ class SalesInvoice(SellingController):
self.validate_debit_to_acc() self.validate_debit_to_acc()
self.validate_fixed_asset_account() self.validate_fixed_asset_account()
self.clear_unallocated_advances("Sales Invoice Advance", "advances") self.clear_unallocated_advances("Sales Invoice Advance", "advances")
self.validate_advance_jv("advances", "sales_order") self.validate_advance_jv("Sales Order")
self.add_remarks() self.add_remarks()
self.validate_write_off_account() self.validate_write_off_account()
@@ -105,7 +105,7 @@ class SalesInvoice(SellingController):
self.check_stop_sales_order("sales_order") self.check_stop_sales_order("sales_order")
from erpnext.accounts.utils import remove_against_link_from_jv from erpnext.accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doctype, self.name, "against_invoice") remove_against_link_from_jv(self.doctype, self.name)
if not self.is_return: if not self.is_return:
self.update_status_updater_args() self.update_status_updater_args()

View File

@@ -391,7 +391,8 @@ class TestSalesInvoice(unittest.TestCase):
import test_records as jv_test_records import test_records as jv_test_records
jv = frappe.get_doc(frappe.copy_doc(jv_test_records[0])) jv = frappe.get_doc(frappe.copy_doc(jv_test_records[0]))
jv.get("accounts")[0].against_invoice = w.name jv.get("accounts")[0].reference_type = w.doctype
jv.get("accounts")[0].reference_name = w.name
jv.insert() jv.insert()
jv.submit() jv.submit()
@@ -656,17 +657,17 @@ class TestSalesInvoice(unittest.TestCase):
si.load_from_db() si.load_from_db()
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account` self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
where against_invoice=%s""", si.name)) where reference_name=%s""", si.name))
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account` self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
where against_invoice=%s and credit=300""", si.name)) where reference_name=%s and credit=300""", si.name))
self.assertEqual(si.outstanding_amount, 261.8) self.assertEqual(si.outstanding_amount, 261.8)
si.cancel() si.cancel()
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account` self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
where against_invoice=%s""", si.name)) where reference_name=%s""", si.name))
def test_recurring_invoice(self): def test_recurring_invoice(self):
from erpnext.controllers.tests.test_recurring_document import test_recurring_document from erpnext.controllers.tests.test_recurring_document import test_recurring_document
@@ -745,8 +746,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_return_sales_invoice(self): def test_return_sales_invoice(self):
set_perpetual_inventory() set_perpetual_inventory()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100)
actual_qty_0 = get_qty_after_transaction() actual_qty_0 = get_qty_after_transaction()

View File

@@ -11,8 +11,7 @@ from erpnext.accounts.utils import validate_expense_against_budget
class StockAccountInvalidTransaction(frappe.ValidationError): pass class StockAccountInvalidTransaction(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
update_outstanding='Yes'):
if gl_map: if gl_map:
if not cancel: if not cancel:
gl_map = process_gl_map(gl_map, merge_entries) gl_map = process_gl_map(gl_map, merge_entries)
@@ -51,7 +50,7 @@ def merge_similar_entries(gl_map):
merged_gl_map.append(entry) merged_gl_map.append(entry)
# filter zero debit and credit entries # filter zero debit and credit entries
merged_gl_map = filter(lambda x: flt(x.debit)!=0 or flt(x.credit)!=0, merged_gl_map) merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map)
return merged_gl_map return merged_gl_map
def check_if_in_list(gle, gl_map): def check_if_in_list(gle, gl_map):

View File

@@ -202,7 +202,7 @@ erpnext.AccountsChart = Class.extend({
title:__('New Account'), title:__('New Account'),
fields: [ fields: [
{fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true, {fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true,
description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers, they are created automatically from the Customer and Supplier master")}, description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers")},
{fieldtype:'Check', fieldname:'is_group', label:__('Is Group'), {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')}, description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
{fieldtype:'Select', fieldname:'account_type', label:__('Account Type'), {fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),

View File

@@ -15,21 +15,19 @@ def execute(filters=None):
entries = get_entries(filters) entries = get_entries(filters)
invoice_posting_date_map = get_invoice_posting_date_map(filters) invoice_posting_date_map = get_invoice_posting_date_map(filters)
against_date = "" against_date = ""
outstanding_amount = 0.0
data = [] data = []
for d in entries: for d in entries:
if d.against_voucher: against_date = invoice_posting_date_map[d.reference_name] or ""
against_date = d.against_voucher and invoice_posting_date_map[d.against_voucher] or "" if d.reference_type=="Purchase Invoice":
payment_amount = flt(d.debit) or -1 * flt(d.credit) payment_amount = flt(d.debit) or -1 * flt(d.credit)
else: else:
against_date = d.against_invoice and invoice_posting_date_map[d.against_invoice] or ""
payment_amount = flt(d.credit) or -1 * flt(d.debit) payment_amount = flt(d.credit) or -1 * flt(d.debit)
row = [d.name, d.party_type, d.party, d.posting_date, d.against_voucher or d.against_invoice, row = [d.name, d.party_type, d.party, d.posting_date, d.reference_name,
against_date, d.debit, d.credit, d.cheque_no, d.cheque_date, d.remark] against_date, d.debit, d.credit, d.cheque_no, d.cheque_date, d.remark]
if d.against_voucher or d.against_invoice: if d.reference_name:
row += get_ageing_data(30, 60, 90, d.posting_date, against_date, payment_amount) row += get_ageing_data(30, 60, 90, d.posting_date, against_date, payment_amount)
else: else:
row += ["", "", "", "", ""] row += ["", "", "", "", ""]
@@ -82,7 +80,7 @@ def get_conditions(filters):
def get_entries(filters): def get_entries(filters):
conditions = get_conditions(filters) conditions = get_conditions(filters)
entries = frappe.db.sql("""select jv.name, jvd.party_type, jvd.party, jv.posting_date, entries = frappe.db.sql("""select jv.name, jvd.party_type, jvd.party, jv.posting_date,
jvd.against_voucher, jvd.against_invoice, jvd.debit, jvd.credit, jvd.reference_type, jvd.reference_name, jvd.debit, jvd.credit,
jv.cheque_no, jv.cheque_date, jv.remark jv.cheque_no, jv.cheque_date, jv.remark
from `tabJournal Entry Account` jvd, `tabJournal Entry` jv from `tabJournal Entry Account` jvd, `tabJournal Entry` jv
where jvd.parent = jv.name and jv.docstatus=1 %s order by jv.name DESC""" % where jvd.parent = jv.name and jv.docstatus=1 %s order by jv.name DESC""" %

View File

@@ -142,13 +142,6 @@ def reconcile_against_document(args):
for d in args: for d in args:
check_if_jv_modified(d) check_if_jv_modified(d)
validate_allocated_amount(d) validate_allocated_amount(d)
against_fld = {
'Journal Entry' : 'against_jv',
'Sales Invoice' : 'against_invoice',
'Purchase Invoice' : 'against_voucher'
}
d['against_fld'] = against_fld[d['against_voucher_type']]
# cancel JV # cancel JV
jv_obj = frappe.get_doc('Journal Entry', d['voucher_no']) jv_obj = frappe.get_doc('Journal Entry', d['voucher_no'])
@@ -173,8 +166,7 @@ def check_if_jv_modified(args):
select t2.{dr_or_cr} from `tabJournal Entry` t1, `tabJournal Entry Account` t2 select t2.{dr_or_cr} from `tabJournal Entry` t1, `tabJournal Entry Account` t2
where t1.name = t2.parent and t2.account = %(account)s where t1.name = t2.parent and t2.account = %(account)s
and t2.party_type = %(party_type)s and t2.party = %(party)s and t2.party_type = %(party_type)s and t2.party = %(party)s
and ifnull(t2.against_voucher, '')='' and ifnull(t2.reference_type, '') in ("", "Sales Order", "Purchase Order")
and ifnull(t2.against_invoice, '')='' and ifnull(t2.against_jv, '')=''
and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
and t1.docstatus=1 """.format(dr_or_cr = args.get("dr_or_cr")), args) and t1.docstatus=1 """.format(dr_or_cr = args.get("dr_or_cr")), args)
@@ -193,7 +185,12 @@ def update_against_doc(d, jv_obj):
""" """
jv_detail = jv_obj.get("accounts", {"name": d["voucher_detail_no"]})[0] jv_detail = jv_obj.get("accounts", {"name": d["voucher_detail_no"]})[0]
jv_detail.set(d["dr_or_cr"], d["allocated_amt"]) jv_detail.set(d["dr_or_cr"], d["allocated_amt"])
jv_detail.set(d["against_fld"], d["against_voucher"])
original_reference_type = jv_detail.reference_type
original_reference_name = jv_detail.reference_name
jv_detail.set("reference_type", d["against_voucher_type"])
jv_detail.set("reference_name", d["against_voucher"])
if d['allocated_amt'] < d['unadjusted_amt']: if d['allocated_amt'] < d['unadjusted_amt']:
jvd = frappe.db.sql("""select cost_center, balance, against_account, is_advance jvd = frappe.db.sql("""select cost_center, balance, against_account, is_advance
@@ -208,6 +205,8 @@ def update_against_doc(d, jv_obj):
ch.set(d['dr_or_cr'], flt(d['unadjusted_amt']) - flt(d['allocated_amt'])) ch.set(d['dr_or_cr'], flt(d['unadjusted_amt']) - flt(d['allocated_amt']))
ch.set(d['dr_or_cr']== 'debit' and 'credit' or 'debit', 0) ch.set(d['dr_or_cr']== 'debit' and 'credit' or 'debit', 0)
ch.against_account = cstr(jvd[0][2]) ch.against_account = cstr(jvd[0][2])
ch.reference_type = original_reference_type
ch.reference_name = original_reference_name
ch.is_advance = cstr(jvd[0][3]) ch.is_advance = cstr(jvd[0][3])
ch.docstatus = 1 ch.docstatus = 1
@@ -215,15 +214,16 @@ def update_against_doc(d, jv_obj):
jv_obj.flags.ignore_validate_update_after_submit = True jv_obj.flags.ignore_validate_update_after_submit = True
jv_obj.save() jv_obj.save()
def remove_against_link_from_jv(ref_type, ref_no, against_field): def remove_against_link_from_jv(ref_type, ref_no):
linked_jv = frappe.db.sql_list("""select parent from `tabJournal Entry Account` linked_jv = frappe.db.sql_list("""select parent from `tabJournal Entry Account`
where `%s`=%s and docstatus < 2""" % (against_field, "%s"), (ref_no)) where reference_type=%s and reference_name=%s and docstatus < 2""", (ref_type, ref_no))
if linked_jv: if linked_jv:
frappe.db.sql("""update `tabJournal Entry Account` set `%s`=null, frappe.db.sql("""update `tabJournal Entry Account`
set reference_type=null, reference_name = null,
modified=%s, modified_by=%s modified=%s, modified_by=%s
where `%s`=%s and docstatus < 2""" % (against_field, "%s", "%s", against_field, "%s"), where reference_type=%s and reference_name=%s
(now(), frappe.session.user, ref_no)) and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
frappe.db.sql("""update `tabGL Entry` frappe.db.sql("""update `tabGL Entry`
set against_voucher_type=null, against_voucher=null, set against_voucher_type=null, against_voucher=null,

View File

@@ -5,6 +5,14 @@ frappe.provide("erpnext.buying");
{% include 'buying/doctype/purchase_common/purchase_common.js' %}; {% include 'buying/doctype/purchase_common/purchase_common.js' %};
frappe.ui.form.on("Purchase Order", {
onload: function(frm) {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
}
});
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
refresh: function(doc, cdt, cdn) { refresh: function(doc, cdt, cdn) {
var me = this; var me = this;
@@ -12,25 +20,32 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
// this.frm.dashboard.reset(); // this.frm.dashboard.reset();
if(doc.docstatus == 1 && doc.status != 'Stopped') { if(doc.docstatus == 1 && doc.status != 'Stopped') {
if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100)
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Purchase Order']);
if(flt(doc.per_billed)==0) {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry);
}
if(flt(doc.per_received, 2) < 100) { if(flt(doc.per_received, 2) < 100) {
cur_frm.add_custom_button(__('Make Purchase Receipt'), this.make_purchase_receipt); cur_frm.add_custom_button(__('Receive'), this.make_purchase_receipt).addClass("btn-primary");
if(doc.is_subcontracted==="Yes") { if(doc.is_subcontracted==="Yes") {
cur_frm.add_custom_button(__('Transfer Material to Supplier'), this.make_stock_entry); cur_frm.add_custom_button(__('Transfer Material to Supplier'), this.make_stock_entry);
} }
} }
if(flt(doc.per_billed, 2) < 100)
cur_frm.add_custom_button(__('Make Invoice'), this.make_purchase_invoice);
if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100) if(flt(doc.per_billed, 2) < 100)
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Purchase Order']); cur_frm.add_custom_button(__('Invoice'), this.make_purchase_invoice);
} else if(doc.docstatus===0) { } else if(doc.docstatus===0) {
cur_frm.cscript.add_from_mappers(); cur_frm.cscript.add_from_mappers();
} }
if(doc.docstatus == 1 && doc.status == 'Stopped') if(doc.docstatus == 1 && doc.status == 'Stopped')
cur_frm.add_custom_button(__('Unstop Purchase Order'), cur_frm.cscript['Unstop Purchase Order']); cur_frm.add_custom_button(__('Unstop'), cur_frm.cscript['Unstop Purchase Order']);
}, },
make_stock_entry: function() { make_stock_entry: function() {
@@ -126,7 +141,21 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
items_add: function(doc, cdt, cdn) { items_add: function(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn); var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, ["schedule_date"]); this.frm.script_manager.copy_from_first_row("items", row, ["schedule_date"]);
},
make_bank_entry: function() {
return frappe.call({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_from_purchase_order",
args: {
"purchase_order": cur_frm.doc.name
},
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
});
} }
}); });
// for backward compatibility: combine new and previous states // for backward compatibility: combine new and previous states

View File

@@ -46,6 +46,8 @@ cur_frm.cscript.make_dashboard = function(doc) {
+ '</b> / <span class="text-muted">' + __("Total Unpaid") + ": <b>" + '</b> / <span class="text-muted">' + __("Total Unpaid") + ": <b>"
+ format_currency(r.message.total_unpaid, r.message.company_currency[0]) + format_currency(r.message.total_unpaid, r.message.company_currency[0])
+ '</b></span>'); + '</b></span>');
} else {
cur_frm.dashboard.set_headline("");
} }
} }
cur_frm.dashboard.set_badge_count(r.message); cur_frm.dashboard.set_badge_count(r.message);

View File

@@ -0,0 +1,6 @@
- For referencing a line in **Journal Entry**, now you can reference by the **Reference Type** and **Reference Name** columns, instead of "Against Sales Invoice", "Against Purchase Invoice", etc.
- Additional Costs in Stock Entry **[Sponsored by PT. Ridho Sribumi Sejahtera]**
Now additional costs like shipping charges, operating costs etc can be added in Stock Entry in item valuation
- **Update Finished Goods** in Production Order can now use the items from **Transfer Materials for Manufacture** step instead of items from the Bill of Materials. This can be configured in Manufacturing Settings
- Added field **Tax ID** in Customer
- Bug fixes in Item, Time Log Batch, Pricing Rule, Salary Slip, Address and Stock Entry

View File

@@ -11,7 +11,7 @@ from erpnext.utilities.transaction_base import TransactionBase
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
from erpnext.controllers.sales_and_purchase_return import validate_return from erpnext.controllers.sales_and_purchase_return import validate_return
force_item_fields = ("item_name", "item_group", "barcode", "brand", "stock_uom") force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
class CustomerFrozen(frappe.ValidationError): pass class CustomerFrozen(frappe.ValidationError): pass
@@ -153,9 +153,10 @@ class AccountsController(TransactionBase):
item.set(fieldname, value) item.set(fieldname, value)
if ret.get("pricing_rule"): if ret.get("pricing_rule"):
for field in ["base_price_list_rate", "price_list_rate", item.set("discount_percentage", ret.get("discount_percentage"))
"discount_percentage", "base_rate", "rate"]: if ret.get("pricing_rule_for") == "Price":
item.set(field, ret.get(field)) item.set("pricing_list_rate", ret.get("pricing_list_rate"))
def set_taxes(self): def set_taxes(self):
if not self.meta.get_field("taxes"): if not self.meta.get_field("taxes"):
@@ -211,29 +212,32 @@ class AccountsController(TransactionBase):
and ifnull(allocated_amount, 0) = 0""" % (childtype, '%s', '%s'), (parentfield, self.name)) and ifnull(allocated_amount, 0) = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
def get_advances(self, account_head, party_type, party, child_doctype, parentfield, dr_or_cr, against_order_field): def get_advances(self, account_head, party_type, party, child_doctype, parentfield, dr_or_cr, against_order_field):
so_list = list(set([d.get(against_order_field) for d in self.get("items") if d.get(against_order_field)])) """Returns list of advances against Account, Party, Reference"""
cond = "" order_list = list(set([d.get(against_order_field) for d in self.get("items") if d.get(against_order_field)]))
if so_list:
cond = "or (ifnull(t2.%s, '') in (%s))" % ("against_" + against_order_field, ', '.join(['%s']*len(so_list))) if not order_list:
return
in_placeholder = ', '.join(['%s'] * len(order_list))
# conver sales_order to "Sales Order"
reference_type = against_order_field.replace("_", " ").title()
res = frappe.db.sql(""" res = frappe.db.sql("""
select select
t1.name as jv_no, t1.remark, t2.{0} as amount, t2.name as jv_detail_no, `against_{1}` as against_order t1.name as jv_no, t1.remark, t2.{0} as amount, t2.name as jv_detail_no,
reference_name as against_order
from from
`tabJournal Entry` t1, `tabJournal Entry Account` t2 `tabJournal Entry` t1, `tabJournal Entry Account` t2
where where
t1.name = t2.parent and t2.account = %s t1.name = t2.parent and t2.account = %s
and t2.party_type=%s and t2.party=%s and t2.party_type = %s and t2.party = %s
and t2.is_advance = 'Yes' and t1.docstatus = 1 and t2.is_advance = 'Yes' and t1.docstatus = 1
and (( and (
ifnull(t2.against_voucher, '') = '' ifnull(t2.reference_type, '')=''
and ifnull(t2.against_invoice, '') = '' or (t2.reference_type = %s and ifnull(t2.reference_name, '') in ({1})))
and ifnull(t2.against_jv, '') = '' order by t1.posting_date""".format(dr_or_cr, in_placeholder),
and ifnull(t2.against_sales_order, '') = '' [account_head, party_type, party, reference_type] + order_list, as_dict=1)
and ifnull(t2.against_purchase_order, '') = ''
) {2})
order by t1.posting_date""".format(dr_or_cr, against_order_field, cond),
[account_head, party_type, party] + so_list, as_dict=1)
self.set(parentfield, []) self.set(parentfield, [])
for d in res: for d in res:
@@ -246,25 +250,26 @@ class AccountsController(TransactionBase):
"allocated_amount": flt(d.amount) if d.against_order else 0 "allocated_amount": flt(d.amount) if d.against_order else 0
}) })
def validate_advance_jv(self, advance_table_fieldname, against_order_field): def validate_advance_jv(self, reference_type):
against_order_field = frappe.scrub(reference_type)
order_list = list(set([d.get(against_order_field) for d in self.get("items") if d.get(against_order_field)])) order_list = list(set([d.get(against_order_field) for d in self.get("items") if d.get(against_order_field)]))
if order_list: if order_list:
account = self.get("debit_to" if self.doctype=="Sales Invoice" else "credit_to") account = self.get("debit_to" if self.doctype=="Sales Invoice" else "credit_to")
jv_against_order = frappe.db.sql("""select parent, %s as against_order jv_against_order = frappe.db.sql("""select parent, reference_name as against_order
from `tabJournal Entry Account` from `tabJournal Entry Account`
where docstatus=1 and account=%s and ifnull(is_advance, 'No') = 'Yes' where docstatus=1 and account=%s and ifnull(is_advance, 'No') = 'Yes'
and ifnull(against_sales_order, '') in (%s) and reference_type=%s
group by parent, against_sales_order""" % and ifnull(reference_name, '') in ({0})
("against_" + against_order_field, '%s', ', '.join(['%s']*len(order_list))), group by parent, reference_name""".format(', '.join(['%s']*len(order_list))),
tuple([account] + order_list), as_dict=1) tuple([account, reference_type] + order_list), as_dict=1)
if jv_against_order: if jv_against_order:
order_jv_map = {} order_jv_map = {}
for d in jv_against_order: for d in jv_against_order:
order_jv_map.setdefault(d.against_order, []).append(d.parent) order_jv_map.setdefault(d.against_order, []).append(d.parent)
advance_jv_against_si = [d.journal_entry for d in self.get(advance_table_fieldname)] advance_jv_against_si = [d.journal_entry for d in self.get("advances")]
for order, jv_list in order_jv_map.items(): for order, jv_list in order_jv_map.items():
for jv in jv_list: for jv in jv_list:
@@ -318,10 +323,8 @@ class AccountsController(TransactionBase):
def set_total_advance_paid(self): def set_total_advance_paid(self):
if self.doctype == "Sales Order": if self.doctype == "Sales Order":
dr_or_cr = "credit" dr_or_cr = "credit"
against_field = "against_sales_order"
else: else:
dr_or_cr = "debit" dr_or_cr = "debit"
against_field = "against_purchase_order"
advance_paid = frappe.db.sql(""" advance_paid = frappe.db.sql("""
select select
@@ -329,8 +332,10 @@ class AccountsController(TransactionBase):
from from
`tabJournal Entry Account` `tabJournal Entry Account`
where where
{against_field} = %s and docstatus = 1 and is_advance = "Yes" """.format(dr_or_cr=dr_or_cr, \ reference_type = %s and
against_field=against_field), self.name) reference_name = %s and
docstatus = 1 and is_advance = "Yes" """.format(dr_or_cr=dr_or_cr),
(self.doctype, self.name))
if advance_paid: if advance_paid:
advance_paid = flt(advance_paid[0][0], self.precision("advance_paid")) advance_paid = flt(advance_paid[0][0], self.precision("advance_paid"))

View File

@@ -80,9 +80,23 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) {
if(doc.status!=="Quotation") if(doc.status!=="Quotation")
cur_frm.add_custom_button(__('Opportunity Lost'), cur_frm.add_custom_button(__('Opportunity Lost'),
cur_frm.cscript['Declare Opportunity Lost'], "icon-remove", "btn-default"); cur_frm.cscript['Declare Opportunity Lost'], "icon-remove", "btn-default");
} }
var frm = cur_frm;
if(frm.perm[0].write && doc.docstatus==0) {
if(frm.doc.status==="Open") {
frm.add_custom_button("Close", function() {
frm.set_value("status", "Closed");
frm.save();
});
} else {
frm.add_custom_button("Reopen", function() {
frm.set_value("status", "Open");
frm.save();
});
}
}
} }
cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) { cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) {

View File

@@ -27,7 +27,7 @@ blogs.
""" """
app_icon = "icon-th" app_icon = "icon-th"
app_color = "#e74c3c" app_color = "#e74c3c"
app_version = "5.5.1" app_version = "5.6.0"
github_link = "https://github.com/frappe/erpnext" github_link = "https://github.com/frappe/erpnext"
error_report_email = "support@erpnext.com" error_report_email = "support@erpnext.com"

View File

@@ -24,13 +24,15 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts'); var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts');
d1.debit = expense[i].sanctioned_amount; d1.debit = expense[i].sanctioned_amount;
d1.account = expense[i].default_account; d1.account = expense[i].default_account;
d1.against_expense_claim = cur_frm.doc.name; d1.reference_type = cur_frm.doc.doctype;
d1.reference_name = cur_frm.doc.name;
} }
// credit to bank // credit to bank
var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts'); var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts');
d1.credit = cur_frm.doc.total_sanctioned_amount; d1.credit = cur_frm.doc.total_sanctioned_amount;
d1.against_expense_claim = cur_frm.doc.name; d1.reference_type = cur_frm.doc.doctype;
d1.reference_name = cur_frm.doc.name;
if(r.message) { if(r.message) {
d1.account = r.message.account; d1.account = r.message.account;
d1.balance = r.message.balance; d1.balance = r.message.balance;

View File

@@ -152,8 +152,8 @@ class SalarySlip(TransactionBase):
self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount) self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount)
for d in self.get("earnings"): for d in self.get("earnings"):
if cint(d.e_depends_on_lwp) == 1: if cint(d.e_depends_on_lwp) == 1:
d.e_modified_amount = rounded(flt(d.e_amount) * flt(self.payment_days) d.e_modified_amount = rounded((flt(d.e_amount) * flt(self.payment_days)
/ cint(self.total_days_in_month), 2) / cint(self.total_days_in_month)), self.precision("e_modified_amount", "earnings"))
elif not self.payment_days: elif not self.payment_days:
d.e_modified_amount = 0 d.e_modified_amount = 0
elif not d.e_modified_amount: elif not d.e_modified_amount:
@@ -164,8 +164,8 @@ class SalarySlip(TransactionBase):
self.total_deduction = 0 self.total_deduction = 0
for d in self.get('deductions'): for d in self.get('deductions'):
if cint(d.d_depends_on_lwp) == 1: if cint(d.d_depends_on_lwp) == 1:
d.d_modified_amount = rounded(flt(d.d_amount) * flt(self.payment_days) d.d_modified_amount = rounded((flt(d.d_amount) * flt(self.payment_days)
/ cint(self.total_days_in_month), 2) / cint(self.total_days_in_month)), self.precision("d_modified_amount", "deductions"))
elif not self.payment_days: elif not self.payment_days:
d.d_modified_amount = 0 d.d_modified_amount = 0
elif not d.d_modified_amount: elif not d.d_modified_amount:
@@ -174,10 +174,13 @@ class SalarySlip(TransactionBase):
self.total_deduction += flt(d.d_modified_amount) self.total_deduction += flt(d.d_modified_amount)
def calculate_net_pay(self): def calculate_net_pay(self):
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
self.calculate_earning_total() self.calculate_earning_total()
self.calculate_ded_total() self.calculate_ded_total()
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
self.rounded_total = rounded(self.net_pay) self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0)
def on_submit(self): def on_submit(self):
if(self.email_check == 1): if(self.email_check == 1):

View File

@@ -41,6 +41,15 @@
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": ""
}, },
{
"default": "BOM",
"fieldname": "backflush_raw_materials_based_on",
"fieldtype": "Select",
"label": "Backflush Raw Materials Based On",
"options": "BOM\nMaterial Transferred for Manufacture",
"permlevel": 0,
"precision": ""
},
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break",

View File

@@ -4,6 +4,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint
from dateutil.relativedelta import relativedelta
class ManufacturingSettings(Document): class ManufacturingSettings(Document):
pass pass
def get_mins_between_operations():
if not hasattr(frappe.local, "_mins_between_operations"):
frappe.local._mins_between_operations = cint(frappe.db.get_single_value("Manufacturing Settings",
"mins_between_operations")) or 10
return relativedelta(minutes=frappe.local._mins_between_operations)

View File

@@ -162,7 +162,7 @@ $.extend(cur_frm.cscript, {
make_se: function(purpose) { make_se: function(purpose) {
var me = this; var me = this;
var max = (purpose === "Manufacture") ? var max = (purpose === "Manufacture") ?
flt(this.frm.doc.qty) - flt(this.frm.doc.produced_qty) : flt(this.frm.doc.material_transferred_for_manufacturing) - flt(this.frm.doc.produced_qty) :
flt(this.frm.doc.qty) - flt(this.frm.doc.material_transferred_for_manufacturing); flt(this.frm.doc.qty) - flt(this.frm.doc.material_transferred_for_manufacturing);
frappe.prompt({fieldtype:"Int", label: __("Qty for {0}", [purpose]), fieldname:"qty", frappe.prompt({fieldtype:"Int", label: __("Qty for {0}", [purpose]), fieldname:"qty",

View File

@@ -10,6 +10,10 @@ from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from erpnext.stock.doctype.item.item import validate_end_of_life from erpnext.stock.doctype.item.item import validate_end_of_life
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
from erpnext.projects.doctype.time_log.time_log import OverlapError
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
class OverProductionError(frappe.ValidationError): pass class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass
@@ -17,9 +21,6 @@ class OperationTooLongError(frappe.ValidationError): pass
class ProductionNotApplicableError(frappe.ValidationError): pass class ProductionNotApplicableError(frappe.ValidationError): pass
class ItemHasVariantError(frappe.ValidationError): pass class ItemHasVariantError(frappe.ValidationError): pass
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
from erpnext.projects.doctype.time_log.time_log import OverlapError
form_grid_templates = { form_grid_templates = {
"operations": "templates/form_grid/production_order_grid.html" "operations": "templates/form_grid/production_order_grid.html"
} }
@@ -231,6 +232,7 @@ class ProductionOrder(Document):
original_start_time = time_log.from_time original_start_time = time_log.from_time
while True: while True:
_from_time = time_log.from_time _from_time = time_log.from_time
try: try:
time_log.save() time_log.save()
break break
@@ -248,6 +250,7 @@ class ProductionOrder(Document):
frappe.msgprint(_("Unable to find Time Slot in the next {0} days for Operation {1}").format(plan_days, d.operation)) frappe.msgprint(_("Unable to find Time Slot in the next {0} days for Operation {1}").format(plan_days, d.operation))
break break
# if time log needs to be moved, make sure that the from time is not the same
if _from_time == time_log.from_time: if _from_time == time_log.from_time:
frappe.throw("Capacity Planning Error") frappe.throw("Capacity Planning Error")
@@ -273,19 +276,13 @@ class ProductionOrder(Document):
d.planned_start_time = self.planned_start_date d.planned_start_time = self.planned_start_date
else: else:
d.planned_start_time = get_datetime(self.operations[i-1].planned_end_time)\ d.planned_start_time = get_datetime(self.operations[i-1].planned_end_time)\
+ self.get_mins_between_operations() + get_mins_between_operations()
d.planned_end_time = get_datetime(d.planned_start_time) + relativedelta(minutes = d.time_in_mins) d.planned_end_time = get_datetime(d.planned_start_time) + relativedelta(minutes = d.time_in_mins)
if d.planned_start_time == d.planned_end_time: if d.planned_start_time == d.planned_end_time:
frappe.throw(_("Capacity Planning Error")) frappe.throw(_("Capacity Planning Error"))
def get_mins_between_operations(self):
if not hasattr(self, "_mins_between_operations"):
self._mins_between_operations = cint(frappe.db.get_single_value("Manufacturing Settings",
"mins_between_operations")) or 10
return relativedelta(minutes=self._mins_between_operations)
def check_operation_fits_in_working_hours(self, d): def check_operation_fits_in_working_hours(self, d):
"""Raises expection if operation is longer than working hours in the given workstation.""" """Raises expection if operation is longer than working hours in the given workstation."""
from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours
@@ -356,7 +353,6 @@ def make_stock_entry(production_order_id, purpose, qty=None):
stock_entry.company = production_order.company stock_entry.company = production_order.company
stock_entry.from_bom = 1 stock_entry.from_bom = 1
stock_entry.bom_no = production_order.bom_no stock_entry.bom_no = production_order.bom_no
stock_entry.additional_operating_cost = production_order.additional_operating_cost
stock_entry.use_multi_level_bom = production_order.use_multi_level_bom stock_entry.use_multi_level_bom = production_order.use_multi_level_bom
stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty)) stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty))
@@ -365,6 +361,8 @@ def make_stock_entry(production_order_id, purpose, qty=None):
else: else:
stock_entry.from_warehouse = production_order.wip_warehouse stock_entry.from_warehouse = production_order.wip_warehouse
stock_entry.to_warehouse = production_order.fg_warehouse stock_entry.to_warehouse = production_order.fg_warehouse
additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty)
stock_entry.set("additional_costs", additional_costs)
stock_entry.get_items() stock_entry.get_items()
return stock_entry.as_dict() return stock_entry.as_dict()

View File

@@ -28,9 +28,9 @@ class TestProductionOrder(unittest.TestCase):
# add raw materials to stores # add raw materials to stores
test_stock_entry.make_stock_entry(item_code="_Test Item", test_stock_entry.make_stock_entry(item_code="_Test Item",
target="Stores - _TC", qty=100, incoming_rate=100) target="Stores - _TC", qty=100, basic_rate=100)
test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="Stores - _TC", qty=100, incoming_rate=100) target="Stores - _TC", qty=100, basic_rate=100)
# from stores to wip # from stores to wip
s = frappe.get_doc(make_stock_entry(pro_order.name, "Material Transfer for Manufacture", 4)) s = frappe.get_doc(make_stock_entry(pro_order.name, "Material Transfer for Manufacture", 4))
@@ -58,9 +58,9 @@ class TestProductionOrder(unittest.TestCase):
pro_doc = self.check_planned_qty() pro_doc = self.check_planned_qty()
test_stock_entry.make_stock_entry(item_code="_Test Item", test_stock_entry.make_stock_entry(item_code="_Test Item",
target="_Test Warehouse - _TC", qty=100, incoming_rate=100) target="_Test Warehouse - _TC", qty=100, basic_rate=100)
test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC", qty=100, incoming_rate=100) target="_Test Warehouse - _TC", qty=100, basic_rate=100)
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 7)) s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 7))
s.insert() s.insert()

View File

@@ -188,3 +188,7 @@ erpnext.patches.v5_4.set_root_and_report_type
erpnext.patches.v5_4.notify_system_managers_regarding_wrong_tax_calculation erpnext.patches.v5_4.notify_system_managers_regarding_wrong_tax_calculation
erpnext.patches.v5_4.fix_invoice_outstanding erpnext.patches.v5_4.fix_invoice_outstanding
execute:frappe.db.sql("update `tabStock Ledger Entry` set stock_queue = '[]' where voucher_type = 'Stock Reconciliation' and ifnull(qty_after_transaction, 0) = 0") execute:frappe.db.sql("update `tabStock Ledger Entry` set stock_queue = '[]' where voucher_type = 'Stock Reconciliation' and ifnull(qty_after_transaction, 0) = 0")
erpnext.patches.v5_4.fix_missing_item_images
erpnext.patches.v5_4.stock_entry_additional_costs
erpnext.patches.v5_4.cleanup_journal_entry
execute:frappe.db.sql("update `tabProduction Order` pro set description = (select description from tabItem where name=pro.production_item) where ifnull(description, '') = ''")

View File

@@ -0,0 +1,15 @@
import frappe
def execute():
frappe.reload_doctype("Journal Entry Account")
for doctype, fieldname in (
("Sales Order", "against_sales_order"),
("Purchase Order", "against_purchase_order"),
("Sales Invoice", "against_invoice"),
("Purchase Invoice", "against_voucher"),
("Journal Entry", "against_jv"),
("Expense Claim", "against_expense_claim"),
):
frappe.db.sql("""update `tabJournal Entry Account`
set reference_type=%s and reference_name={0} where ifnull({0}, '') != ''
""".format(fieldname), doctype)

View File

@@ -0,0 +1,116 @@
from __future__ import unicode_literals
import frappe
import os
from frappe.utils import get_files_path
from frappe.utils.file_manager import get_content_hash
def execute():
files_path = get_files_path()
# get files that don't have attached_to_name but exist
unlinked_files = get_unlinked_files(files_path)
if not unlinked_files:
return
fixed_files = fix_files_for_item(files_path, unlinked_files)
# fix remaining files
for key, file_data in unlinked_files.items():
if key not in fixed_files:
rename_and_set_content_hash(files_path, unlinked_files, key)
frappe.db.commit()
def fix_files_for_item(files_path, unlinked_files):
fixed_files = []
# make a list of files/something and /files/something to check in child table's image column
file_urls = [key for key in unlinked_files.keys()] + ["/" + key for key in unlinked_files.keys()]
file_item_code = get_file_item_code(file_urls)
for (file_url, item_code), children in file_item_code.items():
new_file_url = "/files/{0}".format(unlinked_files[file_url]["file_name"])
for row in children:
# print file_url, new_file_url, item_code, row.doctype, row.name
# replace image in these rows with the new file url
frappe.db.set_value(row.doctype, row.name, "image", new_file_url, update_modified=False)
# set it as attachment of this item code
file_data = frappe.get_doc("File Data", unlinked_files[file_url]["file"])
file_data.attached_to_doctype = "Item"
file_data.attached_to_name = item_code
file_data.save()
# set it as image in Item
if not frappe.db.get_value("Item", item_code, "image"):
frappe.db.set_value("Item", item_code, "image", new_file_url, update_modified=False)
rename_and_set_content_hash(files_path, unlinked_files, file_url)
fixed_files.append(file_url)
# commit
frappe.db.commit()
return fixed_files
def rename_and_set_content_hash(files_path, unlinked_files, file_url):
# rename this file
old_filename = os.path.join(files_path, unlinked_files[file_url]["file"])
new_filename = os.path.join(files_path, unlinked_files[file_url]["file_name"])
if not os.path.exists(new_filename):
os.rename(old_filename, new_filename)
# set content hash if missing
file_data_name = unlinked_files[file_url]["file"]
if not frappe.db.get_value("File Data", file_data_name, "content_hash"):
with open(new_filename, "r") as f:
content_hash = get_content_hash(f.read())
frappe.db.set_value("File Data", file_data_name, "content_hash", content_hash)
def get_unlinked_files(files_path):
# find files that have the same name as a File Data doc
# and the file_name mentioned in that File Data doc doesn't exist
# and it isn't already attached to a doc
unlinked_files = {}
files = os.listdir(files_path)
for file in files:
if not frappe.db.exists("File Data", {"file_name": file}):
file_data = frappe.db.get_value("File Data", {"name": file},
["file_name", "attached_to_doctype", "attached_to_name"], as_dict=True)
if (file_data
and file_data.file_name
and file_data.file_name not in files
and not file_data.attached_to_doctype
and not file_data.attached_to_name):
file_data["file"] = file
unlinked_files["files/{0}".format(file)] = file_data
return unlinked_files
def get_file_item_code(file_urls):
# get a map of file_url, item_code and list of documents where file_url will need to be changed in image field
file_item_code = {}
doctypes = frappe.db.sql_list("""select name from `tabDocType` dt
where istable=1
and exists (select name from `tabDocField` df where df.parent=dt.name and df.fieldname='item_code')
and exists (select name from `tabDocField` df where df.parent=dt.name and df.fieldname='image')""")
for doctype in doctypes:
result = frappe.db.sql("""select name, image, item_code, '{0}' as doctype from `tab{0}`
where image in ({1})""".format(doctype, ", ".join(["%s"]*len(file_urls))),
file_urls, as_dict=True)
for r in result:
key = (r.image, r.item_code)
if key not in file_item_code:
file_item_code[key] = []
file_item_code[key].append(r)
return file_item_code

View File

@@ -0,0 +1,42 @@
# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import flt
def execute():
frappe.reload_doctype("Stock Entry")
frappe.reload_doctype("Stock Entry Detail")
frappe.reload_doctype("Landed Cost Taxes and Charges")
frappe.db.sql("""update `tabStock Entry Detail` sed, `tabStock Entry` se
set sed.valuation_rate=sed.incoming_rate, sed.basic_rate=sed.incoming_rate, sed.basic_amount=sed.amount
where sed.parent = se.name
and (se.purpose not in ('Manufacture', 'Repack') or ifnull(additional_operating_cost, 0)=0)
""")
stock_entries = frappe.db.sql_list("""select name from `tabStock Entry`
where purpose in ('Manufacture', 'Repack') and ifnull(additional_operating_cost, 0)!=0""")
for d in stock_entries:
stock_entry = frappe.get_doc("Stock Entry", d)
stock_entry.append("additional_costs", {
"description": "Additional Operating Cost",
"amount": stock_entry.additional_operating_cost
})
number_of_fg_items = len([t.t_warehouse for t in stock_entry.get("items") if t.t_warehouse])
for d in stock_entry.get("items"):
d.valuation_rate = d.incoming_rate
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1):
d.additional_cost = stock_entry.additional_operating_cost
d.basic_rate = flt(d.valuation_rate) - flt(d.additional_cost)
d.basic_amount = flt(flt(d.basic_rate) *flt(d.transfer_qty), d.precision("basic_amount"))
stock_entry.flags.ignore_validate = True
stock_entry.flags.ignore_validate_update_after_submit = True
stock_entry.save()

View File

@@ -6,6 +6,7 @@ import frappe, json
from frappe import _ from frappe import _
from frappe.utils import cstr, flt, get_datetime, get_time, getdate from frappe.utils import cstr, flt, get_datetime, get_time, getdate
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
class OverlapError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass
class OverProductionLoggedError(frappe.ValidationError): pass class OverProductionLoggedError(frappe.ValidationError): pass
@@ -182,9 +183,14 @@ class TimeLog(Document):
def move_to_next_non_overlapping_slot(self): def move_to_next_non_overlapping_slot(self):
"""If in overlap, set start as the end point of the overlapping time log""" """If in overlap, set start as the end point of the overlapping time log"""
overlapping = self.get_overlap_for("workstation") overlapping = self.get_overlap_for("workstation") \
if overlapping: or self.get_overlap_for("employee") \
self.from_time = get_datetime(overlapping.to_time) + relativedelta(minutes=10) or self.get_overlap_for("user")
if not overlapping:
frappe.throw("Logical error: Must find overlapping")
self.from_time = get_datetime(overlapping.to_time) + get_mins_between_operations()
def get_time_log_summary(self): def get_time_log_summary(self):
"""Returns 'Actual Operating Time'. """ """Returns 'Actual Operating Time'. """

View File

@@ -15,6 +15,8 @@ class TimeLogBatch(Document):
def validate(self): def validate(self):
self.set_status() self.set_status()
self.total_hours = 0.0
self.total_billing_amount = 0.0
for d in self.get("time_logs"): for d in self.get("time_logs"):
tl = frappe.get_doc("Time Log", d.time_log) tl = frappe.get_doc("Time Log", d.time_log)
self.update_time_log_values(d, tl) self.update_time_log_values(d, tl)

View File

@@ -13,30 +13,8 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({
setup_warehouse_query: function() { setup_warehouse_query: function() {
var me = this; var me = this;
var warehouse_query_method = function() { erpnext.queries.setup_queries(this.frm, "Warehouse", function() {
return erpnext.queries.warehouse(me.frm.doc); return erpnext.queries.warehouse(me.frm.doc);
};
var _set_warehouse_query = function(doctype, parentfield) {
var warehouse_link_fields = frappe.meta.get_docfields(doctype, me.frm.doc.name,
{"fieldtype": "Link", "options": "Warehouse"});
$.each(warehouse_link_fields, function(i, df) {
if(parentfield) {
me.frm.set_query(df.fieldname, parentfield, warehouse_query_method);
} else {
me.frm.set_query(df.fieldname, warehouse_query_method);
}
});
};
_set_warehouse_query(me.frm.doc.doctype);
// warehouse field in tables
var table_fields = frappe.meta.get_docfields(me.frm.doc.doctype, me.frm.doc.name,
{"fieldtype": "Table"});
$.each(table_fields, function(i, df) {
_set_warehouse_query(df.options, df.fieldname);
}); });
}, },

View File

@@ -75,3 +75,26 @@ $.extend(erpnext.queries, {
} }
} }
}); });
erpnext.queries.setup_queries = function(frm, options, query_fn) {
var me = this;
var set_query = function(doctype, parentfield) {
var link_fields = frappe.meta.get_docfields(doctype, frm.doc.name,
{"fieldtype": "Link", "options": options});
$.each(link_fields, function(i, df) {
if(parentfield) {
frm.set_query(df.fieldname, parentfield, query_fn);
} else {
frm.set_query(df.fieldname, query_fn);
}
});
};
set_query(frm.doc.doctype);
// warehouse field in tables
$.each(frappe.meta.get_docfields(frm.doc.doctype, frm.doc.name, {"fieldtype": "Table"}),
function(i, df) {
set_query(df.options, df.fieldname);
});
}

View File

@@ -46,6 +46,7 @@ cur_frm.cscript.setup_dashboard = function(doc) {
cur_frm.dashboard.add_doctype_badge("Sales Order", "customer"); cur_frm.dashboard.add_doctype_badge("Sales Order", "customer");
cur_frm.dashboard.add_doctype_badge("Delivery Note", "customer"); cur_frm.dashboard.add_doctype_badge("Delivery Note", "customer");
cur_frm.dashboard.add_doctype_badge("Sales Invoice", "customer"); cur_frm.dashboard.add_doctype_badge("Sales Invoice", "customer");
cur_frm.dashboard.add_doctype_badge("Project", "customer");
return frappe.call({ return frappe.call({
type: "GET", type: "GET",
@@ -62,6 +63,8 @@ cur_frm.cscript.setup_dashboard = function(doc) {
+ '</b> / <span class="text-muted">' + __("Unpaid") + ": <b>" + '</b> / <span class="text-muted">' + __("Unpaid") + ": <b>"
+ format_currency(r.message.total_unpaid, r.message["company_currency"][0]) + format_currency(r.message.total_unpaid, r.message["company_currency"][0])
+ '</b></span>'); + '</b></span>');
} else {
cur_frm.dashboard.set_headline("");
} }
} }
cur_frm.dashboard.set_badge_count(r.message); cur_frm.dashboard.set_badge_count(r.message);

View File

@@ -1,35 +1,62 @@
{ {
"allow_copy": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-06-11 14:26:44", "creation": "2013-06-11 14:26:44",
"custom": 0,
"description": "Buyer of Goods and Services.", "description": "Buyer of Goods and Services.",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Master", "document_type": "Master",
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"fieldname": "basic_info", "fieldname": "basic_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "", "label": "",
"no_copy": 0,
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "icon-user", "options": "icon-user",
"permlevel": 0, "permlevel": 0,
"reqd": 0 "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Series", "label": "Series",
"no_copy": 1, "no_copy": 1,
"options": "CUST-", "options": "CUST-",
"permlevel": 0, "permlevel": 0,
"print_hide": 0 "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "customer_name", "fieldname": "customer_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 0, "in_list_view": 0,
"label": "Full Name", "label": "Full Name",
@@ -38,25 +65,43 @@
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "customer_type", "fieldname": "customer_type",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Type", "label": "Type",
"no_copy": 0,
"oldfieldname": "customer_type", "oldfieldname": "customer_type",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nCompany\nIndividual", "options": "\nCompany\nIndividual",
"permlevel": 0, "permlevel": 0,
"reqd": 1 "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "lead_name", "fieldname": "lead_name",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 0,
"label": "From Lead", "label": "From Lead",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "lead_name", "oldfieldname": "lead_name",
@@ -64,228 +109,580 @@
"options": "Lead", "options": "Lead",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"report_hide": 1 "read_only": 0,
"report_hide": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "column_break0", "fieldname": "column_break0",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50%" "width": "50%"
}, },
{ {
"allow_on_submit": 0,
"description": "", "description": "",
"fieldname": "customer_group", "fieldname": "customer_group",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Customer Group", "label": "Customer Group",
"no_copy": 0,
"oldfieldname": "customer_group", "oldfieldname": "customer_group",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Customer Group", "options": "Customer Group",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"description": "", "description": "",
"fieldname": "territory", "fieldname": "territory",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Territory", "label": "Territory",
"no_copy": 0,
"oldfieldname": "territory", "oldfieldname": "territory",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Territory", "options": "Territory",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"reqd": 1 "read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "tax_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Tax ID",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "is_frozen", "fieldname": "is_frozen",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Frozen", "label": "Is Frozen",
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:!doc.__islocal",
"fieldname": "address_contacts", "fieldname": "address_contacts",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "", "label": "",
"no_copy": 0,
"options": "icon-map-marker", "options": "icon-map-marker",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "address_html", "fieldname": "address_html",
"fieldtype": "HTML", "fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Address HTML", "label": "Address HTML",
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"read_only": 1 "print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "column_break1", "fieldname": "column_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50%" "width": "50%"
}, },
{ {
"allow_on_submit": 0,
"fieldname": "contact_html", "fieldname": "contact_html",
"fieldtype": "HTML", "fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Contact HTML", "label": "Contact HTML",
"no_copy": 0,
"oldfieldtype": "HTML", "oldfieldtype": "HTML",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "default_receivable_accounts", "fieldname": "default_receivable_accounts",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Default Receivable Accounts", "label": "Default Receivable Accounts",
"permlevel": 0 "no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:!doc.__islocal",
"description": "Mention if non-standard receivable account applicable", "description": "Mention if non-standard receivable account applicable",
"fieldname": "accounts", "fieldname": "accounts",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Accounts", "label": "Accounts",
"no_copy": 0,
"options": "Party Account", "options": "Party Account",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "more_info", "fieldname": "more_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "", "label": "",
"no_copy": 0,
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "icon-file-text", "options": "icon-file-text",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "column_break2", "fieldname": "column_break2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50%" "width": "50%"
}, },
{ {
"description": "Your Customer's TAX registration numbers (if applicable) or any general information", "allow_on_submit": 0,
"description": "Additional information regarding the customer.",
"fieldname": "customer_details", "fieldname": "customer_details",
"fieldtype": "Text", "fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Customer Details", "label": "Customer Details",
"no_copy": 0,
"oldfieldname": "customer_details", "oldfieldname": "customer_details",
"oldfieldtype": "Code", "oldfieldtype": "Code",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "column_break3", "fieldname": "column_break3",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50%" "width": "50%"
}, },
{ {
"allow_on_submit": 0,
"fieldname": "default_currency", "fieldname": "default_currency",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_filter": 0,
"in_list_view": 0,
"label": "Currency", "label": "Currency",
"no_copy": 1, "no_copy": 1,
"options": "Currency", "options": "Currency",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "default_price_list", "fieldname": "default_price_list",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_filter": 0,
"in_list_view": 0,
"label": "Price List", "label": "Price List",
"no_copy": 0,
"options": "Price List", "options": "Price List",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "default_taxes_and_charges", "fieldname": "default_taxes_and_charges",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_filter": 0,
"in_list_view": 0,
"label": "Taxes and Charges", "label": "Taxes and Charges",
"no_copy": 0,
"options": "Sales Taxes and Charges Template", "options": "Sales Taxes and Charges Template",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "credit_days_based_on", "fieldname": "credit_days_based_on",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Credit Days Based On", "label": "Credit Days Based On",
"no_copy": 0,
"options": "\nFixed Days\nLast Day of the Next Month", "options": "\nFixed Days\nLast Day of the Next Month",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"depends_on": "eval:doc.credit_days_based_on=='Fixed Days'", "depends_on": "eval:doc.credit_days_based_on=='Fixed Days'",
"fieldname": "credit_days", "fieldname": "credit_days",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Credit Days", "label": "Credit Days",
"no_copy": 0,
"oldfieldname": "credit_days", "oldfieldname": "credit_days",
"oldfieldtype": "Int", "oldfieldtype": "Int",
"permlevel": 1 "permlevel": 1,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "credit_limit", "fieldname": "credit_limit",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Credit Limit", "label": "Credit Limit",
"no_copy": 0,
"oldfieldname": "credit_limit", "oldfieldname": "credit_limit",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 1 "permlevel": 1,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "website", "fieldname": "website",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Website", "label": "Website",
"permlevel": 0 "no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "sales_team_section_break", "fieldname": "sales_team_section_break",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "", "label": "",
"no_copy": 0,
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "icon-group", "options": "icon-group",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "default_sales_partner", "fieldname": "default_sales_partner",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_filter": 0,
"in_list_view": 0,
"label": "Sales Partner", "label": "Sales Partner",
"no_copy": 0,
"oldfieldname": "default_sales_partner", "oldfieldname": "default_sales_partner",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Sales Partner", "options": "Sales Partner",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "default_commission_rate", "fieldname": "default_commission_rate",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Commission Rate", "label": "Commission Rate",
"no_copy": 0,
"oldfieldname": "default_commission_rate", "oldfieldname": "default_commission_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "sales_team", "fieldname": "sales_team",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sales Team Details", "label": "Sales Team Details",
"no_copy": 0,
"oldfieldname": "sales_team", "oldfieldname": "sales_team",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Sales Team", "options": "Sales Team",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "communications", "fieldname": "communications",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Communications", "label": "Communications",
"no_copy": 0,
"options": "Communication", "options": "Communication",
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-user", "icon": "icon-user",
"idx": 1, "idx": 1,
"modified": "2015-07-17 09:38:50.086978", "in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-08-07 20:34:25.761769",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Customer", "name": "Customer",
@@ -294,38 +691,73 @@
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 1,
"cancel": 0,
"create": 1, "create": 1,
"delete": 0, "delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Sales User", "role": "Sales User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0,
"delete": 0, "delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 1, "permlevel": 1,
"print": 0,
"read": 1, "read": 1,
"role": "Sales User" "report": 0,
"role": "Sales User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Sales Manager" "role": "Sales Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
@@ -337,46 +769,108 @@
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0,
"delete": 0, "delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 1, "permlevel": 1,
"print": 0,
"read": 1, "read": 1,
"report": 0,
"role": "Sales Master Manager", "role": "Sales Master Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock User" "role": "Stock User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock Manager" "role": "Stock Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts User" "role": "Accounts User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts Manager" "role": "Accounts Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
} }
], ],
"read_only": 0,
"read_only_onload": 0,
"search_fields": "customer_name,customer_group,territory", "search_fields": "customer_name,customer_group,territory",
"title_field": "customer_name" "title_field": "customer_name"
} }

View File

@@ -119,7 +119,8 @@ def get_dashboard_info(customer):
frappe.msgprint(_("Not permitted"), raise_exception=True) frappe.msgprint(_("Not permitted"), raise_exception=True)
out = {} out = {}
for doctype in ["Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: for doctype in ["Opportunity", "Quotation", "Sales Order", "Delivery Note",
"Sales Invoice", "Project"]:
out[doctype] = frappe.db.get_value(doctype, out[doctype] = frappe.db.get_value(doctype,
{"customer": customer, "docstatus": ["!=", 2] }, "count(*)") {"customer": customer, "docstatus": ["!=", 2] }, "count(*)")

View File

@@ -3,6 +3,14 @@
{% include 'selling/sales_common.js' %} {% include 'selling/sales_common.js' %}
frappe.ui.form.on("Sales Order", {
onload: function(frm) {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
}
});
erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend({ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend({
refresh: function(doc, dt, dn) { refresh: function(doc, dt, dn) {
this._super(); this._super();
@@ -16,29 +24,32 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
// cur_frm.dashboard.add_progress(cint(doc.per_billed) + __("% Billed"), // cur_frm.dashboard.add_progress(cint(doc.per_billed) + __("% Billed"),
// doc.per_billed); // doc.per_billed);
// delivery note
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1)
cur_frm.add_custom_button(__('Make Delivery'), this.make_delivery_note);
// indent // indent
if(!doc.order_type || ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1) if(!doc.order_type || ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1)
cur_frm.add_custom_button(__('Make ') + __('Material Request'), cur_frm.add_custom_button(__('Material Request'), this.make_material_request);
this.make_material_request);
// sales invoice if(flt(doc.per_billed)==0) {
if(flt(doc.per_billed, 2) < 100) { cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry);
cur_frm.add_custom_button(__('Make Invoice'), this.make_sales_invoice);
} }
// stop // stop
if(flt(doc.per_delivered, 2) < 100 || doc.per_billed < 100) if(flt(doc.per_delivered, 2) < 100 || doc.per_billed < 100)
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Sales Order']) cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Sales Order'])
// maintenance // maintenance
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) { if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) {
cur_frm.add_custom_button(__('Make Maint. Visit'), this.make_maintenance_visit); cur_frm.add_custom_button(__('Maint. Visit'), this.make_maintenance_visit);
cur_frm.add_custom_button(__('Make Maint. Schedule'), this.make_maintenance_schedule); cur_frm.add_custom_button(__('Maint. Schedule'), this.make_maintenance_schedule);
} }
// delivery note
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1)
cur_frm.add_custom_button(__('Delivery'), this.make_delivery_note).addClass("btn-primary");
// sales invoice
if(flt(doc.per_billed, 2) < 100) {
cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary");
}
} else { } else {
// un-stop // un-stop
@@ -122,6 +133,20 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
frm: cur_frm frm: cur_frm
}) })
}, },
make_bank_entry: function() {
return frappe.call({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_from_sales_order",
args: {
"sales_order": cur_frm.doc.name
},
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
});
}
}); });
// for backward compatibility: combine new and previous states // for backward compatibility: combine new and previous states

View File

@@ -32,7 +32,7 @@ class Company(Document):
frappe.throw(_("Abbreviation cannot have more than 5 characters")) frappe.throw(_("Abbreviation cannot have more than 5 characters"))
if not self.abbr.strip(): if not self.abbr.strip():
frappe.throw(_("Abbr can not be blank or space")) frappe.throw(_("Abbreviation is mandatory"))
self.previous_default_currency = frappe.db.get_value("Company", self.name, "default_currency") self.previous_default_currency = frappe.db.get_value("Company", self.name, "default_currency")
if self.default_currency and self.previous_default_currency and \ if self.default_currency and self.previous_default_currency and \

View File

@@ -14,22 +14,22 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
// show Make Invoice button only if Delivery Note is not created from Sales Invoice // show Make Invoice button only if Delivery Note is not created from Sales Invoice
var from_sales_invoice = false; var from_sales_invoice = false;
from_sales_invoice = cur_frm.doc.items.some(function(item) { from_sales_invoice = cur_frm.doc.items.some(function(item) {
return item.against_sales_invoice ? true : false; return item.against_sales_invoice ? true : false;
}); });
if(!from_sales_invoice) if(!from_sales_invoice)
cur_frm.add_custom_button(__('Make Invoice'), this.make_sales_invoice); cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary");
} }
if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1) if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1)
cur_frm.add_custom_button(__('Make Installation Note'), this.make_installation_note); cur_frm.add_custom_button(__('Installation Note'), this.make_installation_note);
if (doc.docstatus==1) { if (doc.docstatus==1) {
cur_frm.add_custom_button(__('Make Sales Return'), this.make_sales_return); cur_frm.add_custom_button(__('Sales Return'), this.make_sales_return);
} }
if(doc.docstatus==0 && !doc.__islocal) { if(doc.docstatus==0 && !doc.__islocal) {
cur_frm.add_custom_button(__('Make Packing Slip'), cur_frm.add_custom_button(__('Packing Slip'),
cur_frm.cscript['Make Packing Slip'], frappe.boot.doctype_icons["Packing Slip"]); cur_frm.cscript['Make Packing Slip'], frappe.boot.doctype_icons["Packing Slip"]);
} }

View File

@@ -37,7 +37,7 @@ class TestDeliveryNote(unittest.TestCase):
set_perpetual_inventory(0) set_perpetual_inventory(0)
self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 0) self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 0)
make_stock_entry(target="_Test Warehouse - _TC", qty=5, incoming_rate=100) make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100)
stock_queue = json.loads(get_previous_sle({ stock_queue = json.loads(get_previous_sle({
"item_code": "_Test Item", "item_code": "_Test Item",
@@ -59,7 +59,7 @@ class TestDeliveryNote(unittest.TestCase):
self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1) self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1)
frappe.db.set_value("Item", "_Test Item", "valuation_method", "FIFO") frappe.db.set_value("Item", "_Test Item", "valuation_method", "FIFO")
make_stock_entry(target="_Test Warehouse - _TC", qty=5, incoming_rate=100) make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100)
stock_in_hand_account = frappe.db.get_value("Account", {"warehouse": "_Test Warehouse - _TC"}) stock_in_hand_account = frappe.db.get_value("Account", {"warehouse": "_Test Warehouse - _TC"})
prev_bal = get_balance_on(stock_in_hand_account) prev_bal = get_balance_on(stock_in_hand_account)
@@ -85,7 +85,7 @@ class TestDeliveryNote(unittest.TestCase):
# back dated incoming entry # back dated incoming entry
make_stock_entry(posting_date=add_days(nowdate(), -2), target="_Test Warehouse - _TC", make_stock_entry(posting_date=add_days(nowdate(), -2), target="_Test Warehouse - _TC",
qty=5, incoming_rate=100) qty=5, basic_rate=100)
gl_entries = get_gl_entries("Delivery Note", dn.name) gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
@@ -107,9 +107,9 @@ class TestDeliveryNote(unittest.TestCase):
def test_delivery_note_gl_entry_packing_item(self): def test_delivery_note_gl_entry_packing_item(self):
set_perpetual_inventory() set_perpetual_inventory()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, incoming_rate=100) make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=100)
make_stock_entry(item_code="_Test Item Home Desktop 100", make_stock_entry(item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC", qty=10, incoming_rate=100) target="_Test Warehouse - _TC", qty=10, basic_rate=100)
stock_in_hand_account = frappe.db.get_value("Account", {"warehouse": "_Test Warehouse - _TC"}) stock_in_hand_account = frappe.db.get_value("Account", {"warehouse": "_Test Warehouse - _TC"})
prev_bal = get_balance_on(stock_in_hand_account) prev_bal = get_balance_on(stock_in_hand_account)
@@ -184,7 +184,7 @@ class TestDeliveryNote(unittest.TestCase):
def test_sales_return_for_non_bundled_items(self): def test_sales_return_for_non_bundled_items(self):
set_perpetual_inventory() set_perpetual_inventory()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100) make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
actual_qty_0 = get_qty_after_transaction() actual_qty_0 = get_qty_after_transaction()

View File

@@ -48,7 +48,7 @@ class Item(WebsiteGenerator):
self.website_image = self.image self.website_image = self.image
self.check_warehouse_is_set_for_stock_item() self.check_warehouse_is_set_for_stock_item()
self.check_stock_uom_with_bin() self.validate_uom()
self.add_default_uom_in_conversion_factor_table() self.add_default_uom_in_conversion_factor_table()
self.validate_conversion_factor() self.validate_conversion_factor()
self.validate_item_type() self.validate_item_type()
@@ -105,35 +105,6 @@ class Item(WebsiteGenerator):
[self.remove(d) for d in to_remove] [self.remove(d) for d in to_remove]
def check_stock_uom_with_bin(self):
if not self.get("__islocal"):
if self.stock_uom == frappe.db.get_value("Item", self.name, "stock_uom"):
return
matched=True
ref_uom = frappe.db.get_value("Stock Ledger Entry",
{"item_code": self.name}, "stock_uom")
if ref_uom:
if cstr(ref_uom) != cstr(self.stock_uom):
matched = False
else:
bin_list = frappe.db.sql("select * from tabBin where item_code=%s",
self.item_code, as_dict=1)
for bin in bin_list:
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(self.stock_uom):
matched = False
break
if matched and bin_list:
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""",
(self.stock_uom, self.name))
if not matched:
frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module.").format(self.name))
def update_template_tables(self): def update_template_tables(self):
template = frappe.get_doc("Item", self.variant_of) template = frappe.get_doc("Item", self.variant_of)
@@ -345,6 +316,17 @@ class Item(WebsiteGenerator):
if stock_in: if stock_in:
frappe.throw(_("Item Template cannot have stock or Open Sales/Purchase/Production Orders."), ItemTemplateCannotHaveStock) frappe.throw(_("Item Template cannot have stock or Open Sales/Purchase/Production Orders."), ItemTemplateCannotHaveStock)
def validate_uom(self):
if not self.get("__islocal"):
check_stock_uom_with_bin(self.name, self.stock_uom)
if self.has_variants:
for d in frappe.db.get_all("Item", filters= {"variant_of": self.name}):
check_stock_uom_with_bin(d.name, self.stock_uom)
if self.variant_of:
template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
if template_uom != self.stock_uom:
frappe.throw(_("Default Unit of Measure for Variant must be same as Template"))
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 = frappe.db.get_value("Item", item_code, "end_of_life") end_of_life = frappe.db.get_value("Item", item_code, "end_of_life")
@@ -449,3 +431,30 @@ def invalidate_cache_for_item(doc):
if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group: if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
invalidate_cache_for(doc, doc.old_item_group) invalidate_cache_for(doc, doc.old_item_group)
def check_stock_uom_with_bin(item, stock_uom):
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
return
matched=True
ref_uom = frappe.db.get_value("Stock Ledger Entry",
{"item_code": item}, "stock_uom")
if ref_uom:
if cstr(ref_uom) != cstr(stock_uom):
matched = False
else:
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
for bin in bin_list:
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
matched = False
break
if matched and bin_list:
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
if not matched:
frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because \
you have already made some transaction(s) with another UOM. To change default UOM, \
use 'UOM Replace Utility' tool under Stock module.").format(item))

View File

@@ -47,7 +47,7 @@ class TestItem(unittest.TestCase):
def test_template_cannot_have_stock(self): def test_template_cannot_have_stock(self):
item = self.get_item(10) item = self.get_item(10)
make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, incoming_rate=1) make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, basic_rate=1)
item.has_variants = 1 item.has_variants = 1
self.assertRaises(ItemTemplateCannotHaveStock, item.save) self.assertRaises(ItemTemplateCannotHaveStock, item.save)

View File

@@ -305,7 +305,7 @@ def make_stock_entry(source_name, target_doc=None):
def set_missing_values(source, target): def set_missing_values(source, target):
target.purpose = source.material_request_type target.purpose = source.material_request_type
target.run_method("get_stock_and_rate") target.run_method("calculate_rate_and_amount")
doclist = get_mapped_doc("Material Request", source_name, { doclist = get_mapped_doc("Material Request", source_name, {
"Material Request": { "Material Request": {

View File

@@ -72,7 +72,7 @@ class TestMaterialRequest(unittest.TestCase):
"doctype": "Stock Entry Detail", "doctype": "Stock Entry Detail",
"item_code": "_Test Item Home Desktop 100", "item_code": "_Test Item Home Desktop 100",
"parentfield": "items", "parentfield": "items",
"incoming_rate": 100, "basic_rate": 100,
"qty": qty1, "qty": qty1,
"stock_uom": "_Test UOM 1", "stock_uom": "_Test UOM 1",
"transfer_qty": qty1, "transfer_qty": qty1,
@@ -84,7 +84,7 @@ class TestMaterialRequest(unittest.TestCase):
"doctype": "Stock Entry Detail", "doctype": "Stock Entry Detail",
"item_code": "_Test Item Home Desktop 200", "item_code": "_Test Item Home Desktop 200",
"parentfield": "items", "parentfield": "items",
"incoming_rate": 100, "basic_rate": 100,
"qty": qty2, "qty": qty2,
"stock_uom": "_Test UOM 1", "stock_uom": "_Test UOM 1",
"transfer_qty": qty2, "transfer_qty": qty2,
@@ -196,13 +196,13 @@ class TestMaterialRequest(unittest.TestCase):
"qty": 27.0, "qty": 27.0,
"transfer_qty": 27.0, "transfer_qty": 27.0,
"s_warehouse": "_Test Warehouse 1 - _TC", "s_warehouse": "_Test Warehouse 1 - _TC",
"incoming_rate": 1.0 "basic_rate": 1.0
}) })
se_doc.get("items")[1].update({ se_doc.get("items")[1].update({
"qty": 1.5, "qty": 1.5,
"transfer_qty": 1.5, "transfer_qty": 1.5,
"s_warehouse": "_Test Warehouse 1 - _TC", "s_warehouse": "_Test Warehouse 1 - _TC",
"incoming_rate": 1.0 "basic_rate": 1.0
}) })
# make available the qty in _Test Warehouse 1 before transfer # make available the qty in _Test Warehouse 1 before transfer
@@ -279,13 +279,13 @@ class TestMaterialRequest(unittest.TestCase):
"qty": 60.0, "qty": 60.0,
"transfer_qty": 60.0, "transfer_qty": 60.0,
"s_warehouse": "_Test Warehouse 1 - _TC", "s_warehouse": "_Test Warehouse 1 - _TC",
"incoming_rate": 1.0 "basic_rate": 1.0
}) })
se_doc.get("items")[1].update({ se_doc.get("items")[1].update({
"qty": 3.0, "qty": 3.0,
"transfer_qty": 3.0, "transfer_qty": 3.0,
"s_warehouse": "_Test Warehouse 1 - _TC", "s_warehouse": "_Test Warehouse 1 - _TC",
"incoming_rate": 1.0 "basic_rate": 1.0
}) })
# make available the qty in _Test Warehouse 1 before transfer # make available the qty in _Test Warehouse 1 before transfer
@@ -350,13 +350,13 @@ class TestMaterialRequest(unittest.TestCase):
"transfer_qty": 60.0, "transfer_qty": 60.0,
"s_warehouse": "_Test Warehouse - _TC", "s_warehouse": "_Test Warehouse - _TC",
"t_warehouse": "_Test Warehouse 1 - _TC", "t_warehouse": "_Test Warehouse 1 - _TC",
"incoming_rate": 1.0 "basic_rate": 1.0
}) })
se_doc.get("items")[1].update({ se_doc.get("items")[1].update({
"qty": 3.0, "qty": 3.0,
"transfer_qty": 3.0, "transfer_qty": 3.0,
"s_warehouse": "_Test Warehouse 1 - _TC", "s_warehouse": "_Test Warehouse 1 - _TC",
"incoming_rate": 1.0 "basic_rate": 1.0
}) })
# check for stopped status of Material Request # check for stopped status of Material Request

View File

@@ -52,11 +52,10 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
} }
if(this.frm.doc.docstatus == 1) { if(this.frm.doc.docstatus == 1) {
cur_frm.add_custom_button(__('Return'), this.make_purchase_return);
if(this.frm.doc.__onload && !this.frm.doc.__onload.billing_complete) { if(this.frm.doc.__onload && !this.frm.doc.__onload.billing_complete) {
cur_frm.add_custom_button(__('Make Purchase Invoice'), this.make_purchase_invoice); cur_frm.add_custom_button(__('Invoice'), this.make_purchase_invoice).addClass("btn-primary");
} }
cur_frm.add_custom_button(__('Make Purchase Return'), this.make_purchase_return);
} }
} }

View File

@@ -80,9 +80,9 @@ class TestPurchaseReceipt(unittest.TestCase):
def test_subcontracting(self): def test_subcontracting(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, incoming_rate=100) make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC", make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
qty=100, incoming_rate=100) qty=100, basic_rate=100)
pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes") pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes")
self.assertEquals(len(pr.get("supplied_items")), 2) self.assertEquals(len(pr.get("supplied_items")), 2)

View File

@@ -10,7 +10,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
this.frm.fields_dict.bom_no.get_query = function() { this.frm.fields_dict.bom_no.get_query = function() {
return { return {
filters:{ 'docstatus': 1 } filters:{
"docstatus": 1,
"is_active": 1
}
}; };
}; };
@@ -22,7 +25,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
return { return {
"filters": { "filters": {
"docstatus": 1, "docstatus": 1,
"is_subcontracted": "Yes" "is_subcontracted": "Yes",
"company": me.frm.doc.company
} }
}; };
}); });
@@ -38,6 +42,14 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
} }
} }
} }
this.frm.set_query("difference_account", function() {
return {
"filters": {
"company": me.frm.doc.company,
"is_group": 0
}
};
});
} }
}, },
@@ -122,11 +134,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
d.transfer_qty = flt(d.qty) * flt(d.conversion_factor); d.transfer_qty = flt(d.qty) * flt(d.conversion_factor);
refresh_field('items'); refresh_field('items');
calculate_total(doc, cdt, cdn);
},
incoming_rate: function(doc, cdt, cdn) {
calculate_total(doc, cdt, cdn);
}, },
production_order: function() { production_order: function() {
@@ -135,13 +142,29 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
return frappe.call({ return frappe.call({
method: "erpnext.stock.doctype.stock_entry.stock_entry.get_production_order_details", method: "erpnext.stock.doctype.stock_entry.stock_entry.get_production_order_details",
args: {production_order: this.frm.doc.production_order}, args: {production_order: me.frm.doc.production_order},
callback: function(r) { callback: function(r) {
if (!r.exc) { if (!r.exc) {
me.frm.set_value(r.message); $.each(["from_bom", "bom_no", "fg_completed_qty", "use_multi_level_bom"], function(i, field) {
me.frm.set_value(field, r.message[field]);
})
if (me.frm.doc.purpose == "Material Transfer for Manufacture" && !me.frm.doc.to_warehouse) if (me.frm.doc.purpose == "Material Transfer for Manufacture" && !me.frm.doc.to_warehouse)
me.frm.set_value("to_warehouse", r.message["wip_warehouse"]); me.frm.set_value("to_warehouse", r.message["wip_warehouse"]);
me.frm.set_value("from_bom", 1);
if (me.frm.doc.purpose == "Manufacture") {
if(r.message["additional_costs"].length) {
$.each(r.message["additional_costs"], function(i, row) {
me.frm.add_child("additional_costs", row);
})
refresh_field("additional_costs");
}
if (!me.frm.doc.from_warehouse) me.frm.set_value("from_warehouse", r.message["wip_warehouse"]);
if (!me.frm.doc.to_warehouse) me.frm.set_value("to_warehouse", r.message["fg_warehouse"]);
}
me.get_items()
} }
} }
}); });
@@ -229,13 +252,20 @@ cur_frm.cscript.toggle_related_fields = function(doc) {
if(doc.purpose == "Material Receipt") { if(doc.purpose == "Material Receipt") {
cur_frm.set_value("from_bom", 0); cur_frm.set_value("from_bom", 0);
} }
// Addition costs based on purpose
cur_frm.toggle_display(["additional_costs", "total_additional_costs", "additional_costs_section"],
doc.purpose!='Material Issue');
cur_frm.fields_dict["items"].grid.set_column_disp("additional_cost", doc.purpose!='Material Issue');
} }
cur_frm.fields_dict['production_order'].get_query = function(doc) { cur_frm.fields_dict['production_order'].get_query = function(doc) {
return { return {
filters: [ filters: [
['Production Order', 'docstatus', '=', 1], ['Production Order', 'docstatus', '=', 1],
['Production Order', 'qty', '>','`tabProduction Order`.produced_qty'] ['Production Order', 'qty', '>','`tabProduction Order`.produced_qty'],
['Production Order', 'company', '=', cur_frm.doc.company]
] ]
} }
} }
@@ -376,16 +406,3 @@ cur_frm.cscript.company = function(doc, cdt, cdn) {
cur_frm.cscript.posting_date = function(doc, cdt, cdn){ cur_frm.cscript.posting_date = function(doc, cdt, cdn){
erpnext.get_fiscal_year(doc.company, doc.posting_date); erpnext.get_fiscal_year(doc.company, doc.posting_date);
} }
var calculate_total = function(doc, cdt, cdn){
var d = locals[cdt][cdn];
amount = flt(d.incoming_rate) * flt(d.transfer_qty)
frappe.model.set_value(cdt, cdn, 'amount', amount);
var total_amount = 0.0;
var items = doc.items || [];
for(var i=0;i<items.length;i++) {
total_amount += flt(items[i].amount);
}
doc.total_amount = total_amount;
refresh_field("total_amount");
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,8 +29,7 @@ class StockEntry(StockController):
def onload(self): def onload(self):
if self.docstatus==1: if self.docstatus==1:
for item in self.get("items"): for item in self.get("items"):
item.update(get_available_qty(item.item_code, item.update(get_available_qty(item.item_code, item.s_warehouse))
item.s_warehouse))
def validate(self): def validate(self):
self.pro_doc = None self.pro_doc = None
@@ -46,15 +45,14 @@ class StockEntry(StockController):
self.validate_uom_is_integer("stock_uom", "transfer_qty") self.validate_uom_is_integer("stock_uom", "transfer_qty")
self.validate_warehouse() self.validate_warehouse()
self.validate_production_order() self.validate_production_order()
self.get_stock_and_rate()
self.validate_bom() self.validate_bom()
self.validate_finished_goods() self.validate_finished_goods()
self.validate_with_material_request() self.validate_with_material_request()
self.validate_valuation_rate()
self.set_total_incoming_outgoing_value()
self.set_total_amount()
self.validate_batch() self.validate_batch()
self.set_actual_qty()
self.calculate_rate_and_amount()
def on_submit(self): def on_submit(self):
self.update_stock_ledger() self.update_stock_ledger()
@@ -100,7 +98,7 @@ class StockEntry(StockController):
if f in ["stock_uom", "conversion_factor"] or not item.get(f): if f in ["stock_uom", "conversion_factor"] or not item.get(f):
item.set(f, item_details.get(f)) item.set(f, item_details.get(f))
if self.difference_account: if self.difference_account and not item.expense_account:
item.expense_account = self.difference_account item.expense_account = self.difference_account
if not item.transfer_qty: if not item.transfer_qty:
@@ -214,6 +212,88 @@ class StockEntry(StockController):
frappe.throw(_("Stock Entries already created for Production Order ") frappe.throw(_("Stock Entries already created for Production Order ")
+ self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError) + self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError)
def set_actual_qty(self):
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
for d in self.get('items'):
previous_sle = get_previous_sle({
"item_code": d.item_code,
"warehouse": d.s_warehouse or d.t_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time
})
# get actual stock at source warehouse
d.actual_qty = previous_sle.get("qty_after_transaction") or 0
# validate qty during submit
if d.docstatus==1 and d.s_warehouse and not allow_negative_stock and d.actual_qty < d.transfer_qty:
frappe.throw(_("""Row {0}: Qty not avalable in warehouse {1} on {2} {3}.
Available Qty: {4}, Transfer Qty: {5}""").format(d.idx, d.s_warehouse,
self.posting_date, self.posting_time, d.actual_qty, d.transfer_qty), NegativeStockError)
def calculate_rate_and_amount(self, force=False):
self.set_basic_rate(force)
self.distribute_additional_costs()
self.update_valuation_rate()
self.validate_valuation_rate()
self.set_total_incoming_outgoing_value()
self.set_total_amount()
def set_basic_rate(self, force=False):
"""get stock and incoming rate on posting date"""
raw_material_cost = 0.0
for d in self.get('items'):
args = frappe._dict({
"item_code": d.item_code,
"warehouse": d.s_warehouse or d.t_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": d.s_warehouse and -1*flt(d.transfer_qty) or flt(d.transfer_qty),
"serial_no": d.serial_no,
})
# get basic rate
if not d.bom_no:
if not flt(d.basic_rate) or d.s_warehouse or force:
basic_rate = flt(get_incoming_rate(args), self.precision("basic_rate", d))
if basic_rate > 0:
d.basic_rate = basic_rate
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
if not d.t_warehouse:
raw_material_cost += flt(d.basic_amount)
self.set_basic_rate_for_finished_goods(raw_material_cost)
def set_basic_rate_for_finished_goods(self, raw_material_cost):
if self.purpose in ["Manufacture", "Repack"]:
number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse])
for d in self.get("items"):
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1):
d.basic_rate = flt(raw_material_cost / flt(d.transfer_qty), d.precision("basic_rate"))
d.basic_amount = flt(flt(d.basic_rate) * flt(d.transfer_qty), d.precision("basic_amount"))
def distribute_additional_costs(self):
if self.purpose == "Material Issue":
self.additional_costs = []
self.total_additional_costs = sum([flt(t.amount) for t in self.get("additional_costs")])
total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
for d in self.get("items"):
if d.t_warehouse and total_basic_amount:
d.additional_cost = (flt(d.basic_amount) / total_basic_amount) * self.total_additional_costs
else:
d.additional_cost = 0
def update_valuation_rate(self):
for d in self.get("items"):
d.amount = flt(d.basic_amount + flt(d.additional_cost), d.precision("amount"))
d.valuation_rate = flt(flt(d.basic_rate) + flt(d.additional_cost) / flt(d.transfer_qty),
d.precision("valuation_rate"))
def validate_valuation_rate(self): def validate_valuation_rate(self):
if self.purpose in ["Manufacture", "Repack"]: if self.purpose in ["Manufacture", "Repack"]:
valuation_at_source, valuation_at_target = 0, 0 valuation_at_source, valuation_at_target = 0, 0
@@ -224,100 +304,22 @@ class StockEntry(StockController):
valuation_at_target += flt(d.amount) valuation_at_target += flt(d.amount)
if valuation_at_target + 0.001 < valuation_at_source: if valuation_at_target + 0.001 < valuation_at_source:
frappe.throw(_("Total valuation ({0}) for manufactured or repacked item(s) can not be less than total valuation of raw materials ({1})").format(valuation_at_target, frappe.throw(_("Total valuation ({0}) for manufactured or repacked item(s) can not be less than total valuation of raw materials ({1})")
valuation_at_source)) .format(valuation_at_target, valuation_at_source))
def set_total_incoming_outgoing_value(self): def set_total_incoming_outgoing_value(self):
self.total_incoming_value = self.total_outgoing_value = 0.0 self.total_incoming_value = self.total_outgoing_value = 0.0
for d in self.get("items"): for d in self.get("items"):
if d.s_warehouse:
self.total_incoming_value += flt(d.amount)
if d.t_warehouse: if d.t_warehouse:
self.total_incoming_value += flt(d.amount)
if d.s_warehouse:
self.total_outgoing_value += flt(d.amount) self.total_outgoing_value += flt(d.amount)
self.value_difference = self.total_outgoing_value - self.total_incoming_value self.value_difference = self.total_incoming_value - self.total_outgoing_value
def set_total_amount(self): def set_total_amount(self):
self.total_amount = sum([flt(item.amount) for item in self.get("items")]) self.total_amount = sum([flt(item.amount) for item in self.get("items")])
def get_stock_and_rate(self, force=False):
"""get stock and incoming rate on posting date"""
raw_material_cost = 0.0
if not self.posting_date or not self.posting_time:
frappe.throw(_("Posting date and posting time is mandatory"))
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
for d in self.get('items'):
d.transfer_qty = flt(d.transfer_qty)
args = frappe._dict({
"item_code": d.item_code,
"warehouse": d.s_warehouse or d.t_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
"serial_no": d.serial_no,
})
# get actual stock at source warehouse
d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0
# validate qty during submit
if d.docstatus==1 and d.s_warehouse and not allow_negative_stock and d.actual_qty < d.transfer_qty:
frappe.throw(_("""Row {0}: Qty not avalable in warehouse {1} on {2} {3}.
Available Qty: {4}, Transfer Qty: {5}""").format(d.idx, d.s_warehouse,
self.posting_date, self.posting_time, d.actual_qty, d.transfer_qty), NegativeStockError)
# get incoming rate
if not d.bom_no:
if not flt(d.incoming_rate) or d.s_warehouse or force:
incoming_rate = flt(get_incoming_rate(args), self.precision("incoming_rate", d))
if incoming_rate > 0:
d.incoming_rate = incoming_rate
d.amount = flt(flt(d.transfer_qty) * flt(d.incoming_rate), d.precision("amount"))
if not d.t_warehouse:
raw_material_cost += flt(d.amount)
self.add_operation_cost(raw_material_cost, force)
def add_operation_cost(self, raw_material_cost, force):
"""Adds operating cost if Production Order is set"""
# set incoming rate for fg item
if self.purpose in ["Manufacture", "Repack"]:
number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse])
for d in self.get("items"):
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1):
operation_cost_per_unit = self.get_operation_cost_per_unit(d.bom_no, d.qty)
d.incoming_rate = operation_cost_per_unit + (raw_material_cost / flt(d.transfer_qty))
d.amount = flt(flt(d.transfer_qty) * flt(d.incoming_rate), self.precision("transfer_qty", d))
break
def get_operation_cost_per_unit(self, bom_no, qty):
"""Returns operating cost from Production Order for given `bom_no`"""
operation_cost_per_unit = 0
if self.production_order:
if not getattr(self, "pro_doc", None):
self.pro_doc = frappe.get_doc("Production Order", self.production_order)
for d in self.pro_doc.get("operations"):
if flt(d.completed_qty):
operation_cost_per_unit += flt(d.actual_operating_cost) / flt(d.completed_qty)
else:
operation_cost_per_unit += flt(d.planned_operating_cost) / flt(self.pro_doc.qty)
# set operating cost from BOM if specified.
if not operation_cost_per_unit and bom_no:
bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1)
operation_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)
return operation_cost_per_unit + (flt(self.additional_operating_cost) / flt(qty))
def validate_purchase_order(self): def validate_purchase_order(self):
"""Throw exception if more raw material is transferred against Purchase Order than in """Throw exception if more raw material is transferred against Purchase Order than in
the raw materials supplied table""" the raw materials supplied table"""
@@ -378,7 +380,7 @@ class StockEntry(StockController):
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
"warehouse": cstr(d.t_warehouse), "warehouse": cstr(d.t_warehouse),
"actual_qty": flt(d.transfer_qty), "actual_qty": flt(d.transfer_qty),
"incoming_rate": flt(d.incoming_rate) "incoming_rate": flt(d.valuation_rate)
})) }))
# On cancellation, make stock ledger entry for # On cancellation, make stock ledger entry for
@@ -393,6 +395,32 @@ class StockEntry(StockController):
self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No') self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No')
def get_gl_entries(self, warehouse_account):
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
for d in self.get("items"):
additional_cost = flt(d.additional_cost, d.precision("additional_cost"))
if additional_cost:
gl_entries.append(self.get_gl_dict({
"account": expenses_included_in_valuation,
"against": d.expense_account,
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": additional_cost
}))
gl_entries.append(self.get_gl_dict({
"account": d.expense_account,
"against": expenses_included_in_valuation,
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": -1 * additional_cost # put it as negative credit instead of debit purposefully
}))
return gl_entries
def update_production_order(self): def update_production_order(self):
def _validate_production_order(pro_doc): def _validate_production_order(pro_doc):
if flt(pro_doc.docstatus) != 1: if flt(pro_doc.docstatus) != 1:
@@ -442,7 +470,7 @@ class StockEntry(StockController):
'conversion_factor' : 1, 'conversion_factor' : 1,
'batch_no' : '', 'batch_no' : '',
'actual_qty' : 0, 'actual_qty' : 0,
'incoming_rate' : 0 'basic_rate' : 0
} }
for d in [["Account", "expense_account", "default_expense_account"], for d in [["Account", "expense_account", "default_expense_account"],
["Cost Center", "cost_center", "cost_center"]]: ["Cost Center", "cost_center", "cost_center"]]:
@@ -490,7 +518,7 @@ class StockEntry(StockController):
ret = { ret = {
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0, "actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
"incoming_rate" : get_incoming_rate(args) "basic_rate" : get_incoming_rate(args)
} }
return ret return ret
@@ -498,6 +526,9 @@ class StockEntry(StockController):
self.set('items', []) self.set('items', [])
self.validate_production_order() self.validate_production_order()
if not self.posting_date or not self.posting_time:
frappe.throw(_("Posting date and posting time is mandatory"))
if not getattr(self, "pro_doc", None): if not getattr(self, "pro_doc", None):
self.pro_doc = None self.pro_doc = None
@@ -520,6 +551,12 @@ class StockEntry(StockController):
if self.to_warehouse and self.pro_doc: if self.to_warehouse and self.pro_doc:
for item in item_dict.values(): for item in item_dict.values():
item["to_warehouse"] = self.pro_doc.wip_warehouse item["to_warehouse"] = self.pro_doc.wip_warehouse
self.add_to_stock_entry_detail(item_dict)
elif self.production_order and self.purpose == "Manufacture" and \
frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")== "Material Transferred for Manufacture":
self.get_transfered_raw_materials()
else: else:
if not self.fg_completed_qty: if not self.fg_completed_qty:
frappe.throw(_("Manufacturing Quantity is mandatory")) frappe.throw(_("Manufacturing Quantity is mandatory"))
@@ -530,15 +567,14 @@ class StockEntry(StockController):
item["from_warehouse"] = self.pro_doc.wip_warehouse item["from_warehouse"] = self.pro_doc.wip_warehouse
item["to_warehouse"] = self.to_warehouse if self.purpose=="Subcontract" else "" item["to_warehouse"] = self.to_warehouse if self.purpose=="Subcontract" else ""
self.add_to_stock_entry_detail(item_dict)
# add raw materials to Stock Entry Detail table
self.add_to_stock_entry_detail(item_dict)
# add finished goods item # add finished goods item
if self.purpose in ("Manufacture", "Repack"): if self.purpose in ("Manufacture", "Repack"):
self.load_items_from_bom() self.load_items_from_bom()
self.get_stock_and_rate() self.set_actual_qty()
self.calculate_rate_and_amount()
def load_items_from_bom(self): def load_items_from_bom(self):
if self.production_order: if self.production_order:
@@ -568,13 +604,71 @@ class StockEntry(StockController):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
# item dict = { item_code: {qty, description, stock_uom} } # item dict = { item_code: {qty, description, stock_uom} }
item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded = self.use_multi_level_bom) item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty,
fetch_exploded = self.use_multi_level_bom)
for item in item_dict.values(): for item in item_dict.values():
item.from_warehouse = self.from_warehouse or item.default_warehouse item.from_warehouse = self.from_warehouse or item.default_warehouse
return item_dict return item_dict
def get_transfered_raw_materials(self):
transferred_materials = frappe.db.sql("""
select
item_name, item_code, sum(qty) as qty, sed.t_warehouse as warehouse,
description, stock_uom, expense_account, cost_center
from `tabStock Entry` se,`tabStock Entry Detail` sed
where
se.name = sed.parent and se.docstatus=1 and se.purpose='Material Transfer for Manufacture'
and se.production_order= %s and ifnull(sed.t_warehouse, '') != ''
group by sed.item_code, sed.t_warehouse
""", self.production_order, as_dict=1)
materials_already_backflushed = frappe.db.sql("""
select
item_code, sed.s_warehouse as warehouse, sum(qty) as qty
from
`tabStock Entry` se, `tabStock Entry Detail` sed
where
se.name = sed.parent and se.docstatus=1 and se.purpose='Manufacture'
and se.production_order= %s and ifnull(sed.s_warehouse, '') != ''
group by sed.item_code, sed.s_warehouse
""", self.production_order, as_dict=1)
backflushed_materials= {}
for d in materials_already_backflushed:
backflushed_materials.setdefault(d.item_code,[]).append({d.warehouse: d.qty})
po_qty = frappe.db.sql("""select qty, produced_qty, material_transferred_for_manufacturing from
`tabProduction Order` where name=%s""", self.production_order, as_dict=1)[0]
manufacturing_qty = flt(po_qty.qty)
produced_qty = flt(po_qty.produced_qty)
trans_qty = flt(po_qty.material_transferred_for_manufacturing)
for item in transferred_materials:
qty= item.qty
if manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
qty = (qty/trans_qty) * flt(self.fg_completed_qty)
elif backflushed_materials.get(item.item_code):
for d in backflushed_materials.get(item.item_code):
if d.get(item.warehouse):
qty-= d.get(item.warehouse)
if qty > 0:
self.add_to_stock_entry_detail({
item.item_code: {
"from_warehouse": item.warehouse,
"to_warehouse": "",
"qty": qty,
"item_name": item.item_name,
"description": item.description,
"stock_uom": item.stock_uom,
"expense_account": item.expense_account,
"cost_center": item.buying_cost_center,
}
})
def get_pending_raw_materials(self): def get_pending_raw_materials(self):
""" """
issue (item quantity) that is pending to issue or desire to transfer, issue (item quantity) that is pending to issue or desire to transfer,
@@ -667,12 +761,56 @@ class StockEntry(StockController):
if getdate(self.posting_date) > getdate(expiry_date): if getdate(self.posting_date) > getdate(expiry_date):
frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code)) frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
@frappe.whitelist() @frappe.whitelist()
def get_production_order_details(production_order): def get_production_order_details(production_order):
res = frappe.db.sql("""select bom_no, use_multi_level_bom, wip_warehouse, production_order = frappe.get_doc("Production Order", production_order)
ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, pending_qty_to_produce = flt(production_order.qty) - flt(production_order.produced_qty)
(ifnull(additional_operating_cost, 0) / qty)*(ifnull(qty, 0) - ifnull(produced_qty, 0)) as additional_operating_cost
from `tabProduction Order` where name = %s""", production_order, as_dict=1)
return res and res[0] or {} return {
"from_bom": 1,
"bom_no": production_order.bom_no,
"use_multi_level_bom": production_order.use_multi_level_bom,
"wip_warehouse": production_order.wip_warehouse,
"fg_warehouse": production_order.fg_warehouse,
"fg_completed_qty": pending_qty_to_produce,
"additional_costs": get_additional_costs(production_order, fg_qty=pending_qty_to_produce)
}
def get_additional_costs(production_order=None, bom_no=None, fg_qty=None):
additional_costs = []
operating_cost_per_unit = get_operating_cost_per_unit(production_order, bom_no)
if operating_cost_per_unit:
additional_costs.append({
"description": "Operating Cost as per Production Order / BOM",
"amount": operating_cost_per_unit * flt(fg_qty)
})
if production_order and production_order.additional_operating_cost:
additional_operating_cost_per_unit = \
flt(production_order.additional_operating_cost) / flt(production_order.qty)
additional_costs.append({
"description": "Additional Operating Cost",
"amount": additional_operating_cost_per_unit * flt(fg_qty)
})
return additional_costs
def get_operating_cost_per_unit(production_order=None, bom_no=None):
operating_cost_per_unit = 0
if production_order:
if not bom_no:
bom_no = production_order.bom_no
for d in production_order.get("operations"):
if flt(d.completed_qty):
operating_cost_per_unit += flt(d.actual_operating_cost) / flt(d.completed_qty)
else:
operating_cost_per_unit += flt(d.planned_operating_cost) / flt(production_order.qty)
# Get operating cost from BOM if not found in production_order.
if not operating_cost_per_unit and bom_no:
bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1)
operating_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)
return operating_cost_per_unit

View File

@@ -8,7 +8,7 @@
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"doctype": "Stock Entry Detail", "doctype": "Stock Entry Detail",
"expense_account": "Stock Adjustment - _TC", "expense_account": "Stock Adjustment - _TC",
"incoming_rate": 100, "basic_rate": 100,
"item_code": "_Test Item", "item_code": "_Test Item",
"parentfield": "items", "parentfield": "items",
"qty": 50.0, "qty": 50.0,
@@ -32,7 +32,7 @@
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"doctype": "Stock Entry Detail", "doctype": "Stock Entry Detail",
"expense_account": "Stock Adjustment - _TC", "expense_account": "Stock Adjustment - _TC",
"incoming_rate": 100, "basic_rate": 100,
"item_code": "_Test Item", "item_code": "_Test Item",
"parentfield": "items", "parentfield": "items",
"qty": 40.0, "qty": 40.0,
@@ -57,7 +57,7 @@
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"doctype": "Stock Entry Detail", "doctype": "Stock Entry Detail",
"expense_account": "Stock Adjustment - _TC", "expense_account": "Stock Adjustment - _TC",
"incoming_rate": 100, "basic_rate": 100,
"item_code": "_Test Item", "item_code": "_Test Item",
"parentfield": "items", "parentfield": "items",
"qty": 45.0, "qty": 45.0,
@@ -83,7 +83,7 @@
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"doctype": "Stock Entry Detail", "doctype": "Stock Entry Detail",
"expense_account": "Stock Adjustment - _TC", "expense_account": "Stock Adjustment - _TC",
"incoming_rate": 100, "basic_rate": 100,
"item_code": "_Test Item", "item_code": "_Test Item",
"parentfield": "items", "parentfield": "items",
"qty": 50.0, "qty": 50.0,
@@ -97,7 +97,7 @@
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"doctype": "Stock Entry Detail", "doctype": "Stock Entry Detail",
"expense_account": "Stock Adjustment - _TC", "expense_account": "Stock Adjustment - _TC",
"incoming_rate": 5000, "basic_rate": 5000,
"item_code": "_Test Item Home Desktop 100", "item_code": "_Test Item Home Desktop 100",
"parentfield": "items", "parentfield": "items",
"qty": 1, "qty": 1,

View File

@@ -36,12 +36,12 @@ class TestStockEntry(unittest.TestCase):
create_stock_reconciliation(item_code="_Test Item 2", warehouse="_Test Warehouse - _TC", create_stock_reconciliation(item_code="_Test Item 2", warehouse="_Test Warehouse - _TC",
qty=0, rate=100) qty=0, rate=100)
make_stock_entry(item_code=item_code, target=warehouse, qty=1, incoming_rate=10) make_stock_entry(item_code=item_code, target=warehouse, qty=1, basic_rate=10)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0] sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[1, 10]], eval(sle.stock_queue)) self.assertEqual([[1, 10]], eval(sle.stock_queue))
# negative qty # negative qty
make_stock_entry(item_code=item_code, source=warehouse, qty=2, incoming_rate=10) make_stock_entry(item_code=item_code, source=warehouse, qty=2, basic_rate=10)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0] sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[-1, 10]], eval(sle.stock_queue)) self.assertEqual([[-1, 10]], eval(sle.stock_queue))
@@ -53,12 +53,12 @@ class TestStockEntry(unittest.TestCase):
self.assertEqual([[-2, 10]], eval(sle.stock_queue)) self.assertEqual([[-2, 10]], eval(sle.stock_queue))
# move stock to positive # move stock to positive
make_stock_entry(item_code=item_code, target=warehouse, qty=3, incoming_rate=20) make_stock_entry(item_code=item_code, target=warehouse, qty=3, basic_rate=20)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0] sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[1, 20]], eval(sle.stock_queue)) self.assertEqual([[1, 20]], eval(sle.stock_queue))
# incoming entry with diff rate # incoming entry with diff rate
make_stock_entry(item_code=item_code, target=warehouse, qty=1, incoming_rate=30) make_stock_entry(item_code=item_code, target=warehouse, qty=1, basic_rate=30)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0] sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[1, 20],[1, 30]], eval(sle.stock_queue)) self.assertEqual([[1, 20],[1, 30]], eval(sle.stock_queue))
@@ -125,7 +125,7 @@ class TestStockEntry(unittest.TestCase):
set_perpetual_inventory() set_perpetual_inventory()
mr = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", mr = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
qty=50, incoming_rate=100) qty=50, basic_rate=100)
stock_in_hand_account = frappe.db.get_value("Account", {"account_type": "Warehouse", stock_in_hand_account = frappe.db.get_value("Account", {"account_type": "Warehouse",
"warehouse": mr.get("items")[0].t_warehouse}) "warehouse": mr.get("items")[0].t_warehouse})
@@ -152,7 +152,7 @@ class TestStockEntry(unittest.TestCase):
set_perpetual_inventory() set_perpetual_inventory()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
qty=50, incoming_rate=100) qty=50, basic_rate=100)
mi = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC", qty=40) mi = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC", qty=40)
@@ -217,9 +217,9 @@ class TestStockEntry(unittest.TestCase):
def test_repack_no_change_in_valuation(self): def test_repack_no_change_in_valuation(self):
set_perpetual_inventory(0) set_perpetual_inventory(0)
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100) make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
qty=50, incoming_rate=100) qty=50, basic_rate=100)
repack = frappe.copy_doc(test_records[3]) repack = frappe.copy_doc(test_records[3])
repack.posting_date = nowdate() repack.posting_date = nowdate()
@@ -238,15 +238,24 @@ class TestStockEntry(unittest.TestCase):
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_repack_with_change_in_valuation(self): def test_repack_with_additional_costs(self):
set_perpetual_inventory() set_perpetual_inventory()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100) make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
repack = frappe.copy_doc(test_records[3]) repack = frappe.copy_doc(test_records[3])
repack.posting_date = nowdate() repack.posting_date = nowdate()
repack.posting_time = nowtime() repack.posting_time = nowtime()
repack.additional_operating_cost = 1000.0
repack.set("additional_costs", [
{
"description": "Actual Oerating Cost",
"amount": 1000
},
{
"description": "additional operating costs",
"amount": 200
},
])
repack.insert() repack.insert()
repack.submit() repack.submit()
@@ -261,10 +270,12 @@ class TestStockEntry(unittest.TestCase):
stock_value_diff = flt(fg_stock_value_diff - rm_stock_value_diff, 2) stock_value_diff = flt(fg_stock_value_diff - rm_stock_value_diff, 2)
self.assertEqual(stock_value_diff, 1200)
self.check_gl_entries("Stock Entry", repack.name, self.check_gl_entries("Stock Entry", repack.name,
sorted([ sorted([
[stock_in_hand_account, stock_value_diff, 0.0], [stock_in_hand_account, 1200, 0.0],
["Stock Adjustment - _TC", 0.0, stock_value_diff], ["Expenses Included In Valuation - _TC", 0.0, 1200.0]
]) ])
) )
set_perpetual_inventory(0) set_perpetual_inventory(0)
@@ -294,7 +305,6 @@ class TestStockEntry(unittest.TestCase):
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
gl_entries.sort(key=lambda x: x[0]) gl_entries.sort(key=lambda x: x[0])
for i, gle in enumerate(gl_entries): for i, gle in enumerate(gl_entries):
self.assertEquals(expected_gl_entries[i][0], gle[0]) self.assertEquals(expected_gl_entries[i][0], gle[0])
self.assertEquals(expected_gl_entries[i][1], gle[1]) self.assertEquals(expected_gl_entries[i][1], gle[1])
@@ -503,6 +513,8 @@ class TestStockEntry(unittest.TestCase):
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0) frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0)
def test_production_order(self): def test_production_order(self):
from erpnext.manufacturing.doctype.production_order.production_order \
import make_stock_entry as _make_stock_entry
bom_no, bom_operation_cost = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", bom_no, bom_operation_cost = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
"is_default": 1, "docstatus": 1}, ["name", "operating_cost"]) "is_default": 1, "docstatus": 1}, ["name", "operating_cost"])
@@ -514,22 +526,15 @@ class TestStockEntry(unittest.TestCase):
"bom_no": bom_no, "bom_no": bom_no,
"qty": 1.0, "qty": 1.0,
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"wip_warehouse": "_Test Warehouse - _TC" "wip_warehouse": "_Test Warehouse - _TC",
"additional_operating_cost": 1000
}) })
production_order.insert() production_order.insert()
production_order.submit() production_order.submit()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100) make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
stock_entry = frappe.new_doc("Stock Entry") stock_entry = _make_stock_entry(production_order.name, "Manufacture", 1)
stock_entry.update({
"purpose": "Manufacture",
"production_order": production_order.name,
"bom_no": bom_no,
"fg_completed_qty": "1",
"additional_operating_cost": 1000
})
stock_entry.get_items()
rm_cost = 0 rm_cost = 0
for d in stock_entry.get("items"): for d in stock_entry.get("items"):
@@ -538,7 +543,7 @@ class TestStockEntry(unittest.TestCase):
fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount
self.assertEqual(fg_cost, self.assertEqual(fg_cost,
flt(rm_cost + bom_operation_cost + stock_entry.additional_operating_cost, 2)) flt(rm_cost + bom_operation_cost + production_order.additional_operating_cost, 2))
def test_variant_production_order(self): def test_variant_production_order(self):
@@ -610,7 +615,7 @@ def make_stock_entry(**args):
"s_warehouse": args.from_warehouse or args.source, "s_warehouse": args.from_warehouse or args.source,
"t_warehouse": args.to_warehouse or args.target, "t_warehouse": args.to_warehouse or args.target,
"qty": args.qty, "qty": args.qty,
"incoming_rate": args.incoming_rate, "basic_rate": args.basic_rate,
"expense_account": args.expense_account or "Stock Adjustment - _TC", "expense_account": args.expense_account or "Stock Adjustment - _TC",
"conversion_factor": 1.0, "conversion_factor": 1.0,
"cost_center": "_Test Cost Center - _TC" "cost_center": "_Test Cost Center - _TC"

View File

@@ -1,25 +1,58 @@
{ {
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash", "autoname": "hash",
"creation": "2013-03-29 18:22:12", "creation": "2013-03-29 18:22:12",
"custom": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"fieldname": "barcode", "fieldname": "barcode",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Barcode", "label": "Barcode",
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "section_break_2", "fieldname": "section_break_2",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "s_warehouse", "fieldname": "s_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Source Warehouse", "label": "Source Warehouse",
@@ -28,16 +61,38 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Warehouse", "options": "Warehouse",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "col_break1", "fieldname": "col_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "t_warehouse", "fieldname": "t_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Target Warehouse", "label": "Target Warehouse",
@@ -46,240 +101,631 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Warehouse", "options": "Warehouse",
"permlevel": 0, "permlevel": 0,
"read_only": 0 "print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "sec_break1", "fieldname": "sec_break1",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"permlevel": 0 "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "item_code", "fieldname": "item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Item Code", "label": "Item Code",
"no_copy": 0,
"oldfieldname": "item_code", "oldfieldname": "item_code",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Item", "options": "Item",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 0, "read_only": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "col_break2", "fieldname": "col_break2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "qty", "fieldname": "qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Qty", "label": "Qty",
"no_copy": 0,
"oldfieldname": "qty", "oldfieldname": "qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "section_break_8", "fieldname": "section_break_8",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "item_name", "fieldname": "item_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Item Name", "label": "Item Name",
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "description", "fieldname": "description",
"fieldtype": "Text", "fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Description", "label": "Description",
"no_copy": 0,
"oldfieldname": "description", "oldfieldname": "description",
"oldfieldtype": "Text", "oldfieldtype": "Text",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"print_width": "300px", "print_width": "300px",
"read_only": 0, "read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "300px" "width": "300px"
}, },
{ {
"allow_on_submit": 0,
"fieldname": "column_break_10", "fieldname": "column_break_10",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Image", "label": "Image",
"no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 1 "print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "image_view", "fieldname": "image_view",
"fieldtype": "Image", "fieldtype": "Image",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Image View", "label": "Image View",
"no_copy": 0,
"options": "image", "options": "image",
"permlevel": 0, "permlevel": 0,
"precision": "" "precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "quantity_and_rate", "fieldname": "quantity_and_rate",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Quantity and Rate", "label": "Quantity and Rate",
"permlevel": 0 "no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"fieldname": "incoming_rate", "allow_on_submit": 0,
"fieldname": "basic_rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Valuation Rate", "label": "Basic Rate (as per Stock UOM)",
"no_copy": 0,
"oldfieldname": "incoming_rate", "oldfieldname": "incoming_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"print_hide": 1,
"read_only": 0, "read_only": 0,
"reqd": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "basic_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Basic Amount",
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "additional_cost",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Additional Cost",
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "amount", "fieldname": "amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Amount", "label": "Amount",
"no_copy": 0,
"oldfieldname": "amount", "oldfieldname": "amount",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valuation Rate",
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "col_break3", "fieldname": "col_break3",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "uom", "fieldname": "uom",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "UOM", "label": "UOM",
"no_copy": 0,
"oldfieldname": "uom", "oldfieldname": "uom",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "UOM", "options": "UOM",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 0, "read_only": 0,
"reqd": 1 "report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "conversion_factor", "fieldname": "conversion_factor",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Conversion Factor", "label": "Conversion Factor",
"no_copy": 0,
"oldfieldname": "conversion_factor", "oldfieldname": "conversion_factor",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"reqd": 1 "report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "stock_uom", "fieldname": "stock_uom",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0,
"label": "Stock UOM", "label": "Stock UOM",
"no_copy": 0,
"oldfieldname": "stock_uom", "oldfieldname": "stock_uom",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "UOM", "options": "UOM",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0 "search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "transfer_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Qty as per Stock UOM",
"no_copy": 0,
"oldfieldname": "transfer_qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 1,
"read_only": 1,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "serial_no_batch", "fieldname": "serial_no_batch",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Serial No / Batch", "label": "Serial No / Batch",
"permlevel": 0 "no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "serial_no", "fieldname": "serial_no",
"fieldtype": "Text", "fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Serial No", "label": "Serial No",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "serial_no", "oldfieldname": "serial_no",
"oldfieldtype": "Text", "oldfieldtype": "Text",
"permlevel": 0, "permlevel": 0,
"print_hide": 0,
"read_only": 0, "read_only": 0,
"reqd": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "col_break4", "fieldname": "col_break4",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "batch_no", "fieldname": "batch_no",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Batch No", "label": "Batch No",
"no_copy": 0,
"oldfieldname": "batch_no", "oldfieldname": "batch_no",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Batch", "options": "Batch",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"read_only": 0 "read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "accounting", "fieldname": "accounting",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Accounting", "label": "Accounting",
"permlevel": 0 "no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)",
"fieldname": "expense_account", "fieldname": "expense_account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Difference Account", "label": "Difference Account",
"no_copy": 0,
"options": "Account", "options": "Account",
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "col_break5", "fieldname": "col_break5",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"default": ":Company", "default": ":Company",
"depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)", "depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)",
"fieldname": "cost_center", "fieldname": "cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Cost Center", "label": "Cost Center",
"no_copy": 0,
"options": "Cost Center", "options": "Cost Center",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 0, "read_only": 0,
"reqd": 0 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "more_info", "fieldname": "more_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "More Info", "label": "More Info",
"permlevel": 0 "no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"fieldname": "actual_qty", "fieldname": "actual_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 1, "in_filter": 1,
"in_list_view": 0,
"label": "Actual Qty (at source/target)", "label": "Actual Qty (at source/target)",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "actual_qty", "oldfieldname": "actual_qty",
@@ -287,67 +733,107 @@
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 1 "search_index": 1,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"description": "BOM No. for a Finished Good Item", "description": "BOM No. for a Finished Good Item",
"fieldname": "bom_no", "fieldname": "bom_no",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "BOM No", "label": "BOM No",
"no_copy": 0, "no_copy": 0,
"options": "BOM", "options": "BOM",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 0 "read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "col_break6", "fieldname": "col_break6",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"permlevel": 0 "hidden": 0,
}, "ignore_user_permissions": 0,
{ "in_filter": 0,
"fieldname": "transfer_qty", "in_list_view": 0,
"fieldtype": "Float", "no_copy": 0,
"label": "Qty as per Stock UOM",
"oldfieldname": "transfer_qty",
"oldfieldtype": "Currency",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 0,
"read_only": 1, "read_only": 0,
"reqd": 1 "report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"description": "Material Request used to make this Stock Entry", "description": "Material Request used to make this Stock Entry",
"fieldname": "material_request", "fieldname": "material_request",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Material Request", "label": "Material Request",
"no_copy": 1, "no_copy": 1,
"options": "Material Request", "options": "Material Request",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"fieldname": "material_request_item", "fieldname": "material_request_item",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Material Request Item", "label": "Material Request Item",
"no_copy": 1, "no_copy": 1,
"options": "Material Request Item", "options": "Material Request Item",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1, "idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"modified": "2015-07-02 05:32:56.511570", "modified": "2015-08-07 13:21:23.840052",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Detail", "name": "Stock Entry Detail",
"owner": "Administrator", "owner": "Administrator",
"permissions": [] "permissions": [],
"read_only": 0,
"read_only_onload": 0
} }

View File

@@ -82,13 +82,13 @@ class TestStockReconciliation(unittest.TestCase):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item", make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=10, incoming_rate=700) target="_Test Warehouse - _TC", qty=10, basic_rate=700)
make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item", make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item",
source="_Test Warehouse - _TC", qty=15) source="_Test Warehouse - _TC", qty=15)
make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item", make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=15, incoming_rate=1200) target="_Test Warehouse - _TC", qty=15, basic_rate=1200)
def create_stock_reconciliation(**args): def create_stock_reconciliation(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -10,6 +10,28 @@ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
class StockUOMReplaceUtility(Document): class StockUOMReplaceUtility(Document):
# Update Stock UOM
def update_stock_uom(self):
self.validate_item()
self.validate_mandatory()
self.validate_uom_integer_type()
update_stock_ledger_entry(self.item_code, self.new_stock_uom, self.conversion_factor)
update_bin(self.item_code, self.new_stock_uom, self.conversion_factor)
update_item_master(self.item_code, self.new_stock_uom, self.conversion_factor)
#if item is template change UOM for all associated variants
if frappe.db.get_value("Item", self.item_code, "has_variants"):
for d in frappe.db.get_all("Item", filters= {"variant_of": self.item_code}):
update_stock_ledger_entry(d.name, self.new_stock_uom, self.conversion_factor)
update_bin(d.name, self.new_stock_uom, self.conversion_factor)
update_item_master(d.name, self.new_stock_uom, self.conversion_factor)
def validate_item(self):
if frappe.db.get_value("Item", self.item_code, "variant_of"):
frappe.throw(_("You cannot change default UOM of Variant. To change default UOM for Variant change default UOM of the Template"))
def validate_mandatory(self): def validate_mandatory(self):
if not cstr(self.item_code): if not cstr(self.item_code):
frappe.throw(_("Item is required")) frappe.throw(_("Item is required"))
@@ -28,71 +50,6 @@ class StockUOMReplaceUtility(Document):
if cstr(self.new_stock_uom) == cstr(stock_uom): if cstr(self.new_stock_uom) == cstr(stock_uom):
frappe.throw(_("Item is updated")) frappe.throw(_("Item is updated"))
def update_item_master(self):
item_doc = frappe.get_doc("Item", self.item_code)
item_doc.stock_uom = self.new_stock_uom
item_doc.save()
frappe.msgprint(_("Stock UOM updated for Item {0}").format(self.item_code))
def update_bin(self):
# update bin
if flt(self.conversion_factor) != flt(1):
frappe.db.sql("""update `tabBin`
set stock_uom = %s,
indented_qty = ifnull(indented_qty,0) * %s,
ordered_qty = ifnull(ordered_qty,0) * %s,
reserved_qty = ifnull(reserved_qty,0) * %s,
planned_qty = ifnull(planned_qty,0) * %s,
projected_qty = actual_qty + ordered_qty + indented_qty +
planned_qty - reserved_qty
where item_code = %s""", (self.new_stock_uom, self.conversion_factor,
self.conversion_factor, self.conversion_factor,
self.conversion_factor, self.item_code))
else:
frappe.db.sql("update `tabBin` set stock_uom = %s where item_code = %s",
(self.new_stock_uom, self.item_code) )
# acknowledge user
frappe.msgprint(_("Stock balances updated"))
def update_stock_ledger_entry(self):
# update stock ledger entry
from erpnext.stock.stock_ledger import update_entries_after
if flt(self.conversion_factor) != flt(1):
frappe.db.sql("""update `tabStock Ledger Entry`
set stock_uom = %s, actual_qty = ifnull(actual_qty,0) * %s
where item_code = %s""",
(self.new_stock_uom, self.conversion_factor, self.item_code))
else:
frappe.db.sql("""update `tabStock Ledger Entry` set stock_uom=%s
where item_code=%s""", (self.new_stock_uom, self.item_code))
# acknowledge user
frappe.msgprint(_("Stock Ledger entries balances updated"))
# update item valuation
if flt(self.conversion_factor) != flt(1):
wh = frappe.db.sql("select name from `tabWarehouse`")
for w in wh:
update_entries_after({"item_code": self.item_code, "warehouse": w[0]})
# acknowledge user
frappe.msgprint(_("Item valuation updated"))
# Update Stock UOM
def update_stock_uom(self):
self.validate_mandatory()
self.validate_uom_integer_type()
self.update_stock_ledger_entry()
self.update_bin()
self.update_item_master()
def validate_uom_integer_type(self): def validate_uom_integer_type(self):
current_is_integer = frappe.db.get_value("UOM", self.current_stock_uom, "must_be_whole_number") current_is_integer = frappe.db.get_value("UOM", self.current_stock_uom, "must_be_whole_number")
new_is_integer = frappe.db.get_value("UOM", self.new_stock_uom, "must_be_whole_number") new_is_integer = frappe.db.get_value("UOM", self.new_stock_uom, "must_be_whole_number")
@@ -103,6 +60,53 @@ class StockUOMReplaceUtility(Document):
if current_is_integer and new_is_integer and cint(self.conversion_factor)!=self.conversion_factor: if current_is_integer and new_is_integer and cint(self.conversion_factor)!=self.conversion_factor:
frappe.throw(_("Conversion factor cannot be in fractions")) frappe.throw(_("Conversion factor cannot be in fractions"))
def update_item_master(item_code, new_stock_uom, conversion_factor):
frappe.db.set_value("Item", item_code, "stock_uom", new_stock_uom)
frappe.msgprint(_("Stock UOM updated for Item {0}").format(item_code))
def update_bin(item_code, new_stock_uom, conversion_factor):
# update bin
if flt(conversion_factor) != flt(1):
frappe.db.sql("""update `tabBin`
set stock_uom = %s,
indented_qty = ifnull(indented_qty,0) * %s,
ordered_qty = ifnull(ordered_qty,0) * %s,
reserved_qty = ifnull(reserved_qty,0) * %s,
planned_qty = ifnull(planned_qty,0) * %s,
projected_qty = actual_qty + ordered_qty + indented_qty +
planned_qty - reserved_qty
where item_code = %s""", (new_stock_uom, conversion_factor,
conversion_factor, conversion_factor,
conversion_factor, item_code))
else:
frappe.db.sql("update `tabBin` set stock_uom = %s where item_code = %s",
(new_stock_uom, item_code) )
def update_stock_ledger_entry(item_code, new_stock_uom, conversion_factor):
# update stock ledger entry
from erpnext.stock.stock_ledger import update_entries_after
if flt(conversion_factor) != flt(1):
frappe.db.sql("""update `tabStock Ledger Entry`
set stock_uom = %s, actual_qty = ifnull(actual_qty,0) * %s
where item_code = %s""",
(new_stock_uom, conversion_factor, item_code))
else:
frappe.db.sql("""update `tabStock Ledger Entry` set stock_uom=%s
where item_code=%s""", (new_stock_uom, item_code))
# acknowledge user
frappe.msgprint(_("Stock Ledger entries balances updated"))
# update item valuation
if flt(conversion_factor) != flt(1):
wh = frappe.db.sql("select name from `tabWarehouse`")
for w in wh:
update_entries_after({"item_code": item_code, "warehouse": w[0]})
# acknowledge user
frappe.msgprint(_("Item valuation updated"))
@frappe.whitelist() @frappe.whitelist()
def get_stock_uom(item_code): def get_stock_uom(item_code):
return { 'current_stock_uom': cstr(frappe.db.get_value('Item', item_code, 'stock_uom')) } return { 'current_stock_uom': cstr(frappe.db.get_value('Item', item_code, 'stock_uom')) }

View File

@@ -401,7 +401,6 @@ def apply_price_list_on_item(args):
item_details = frappe._dict() item_details = frappe._dict()
item_doc = frappe.get_doc("Item", args.item_code) item_doc = frappe.get_doc("Item", args.item_code)
get_price_list_rate(args, item_doc, item_details) get_price_list_rate(args, item_doc, item_details)
item_details.discount_percentage = 0.0
item_details.update(get_pricing_rule_for_item(args)) item_details.update(get_pricing_rule_for_item(args))
return item_details return item_details

View File

@@ -1,6 +1,6 @@
{% var visible_columns = row.get_visible_columns(["item_code", {% var visible_columns = row.get_visible_columns(["item_code",
"item_name", "amount", "stock_uom", "uom", "qty", "item_name", "amount", "stock_uom", "uom", "qty",
"s_warehouse", "t_warehouse", "incoming_rate"]); "s_warehouse", "t_warehouse", "valuation_rate"]);
%} %}
{% if(!doc) { %} {% if(!doc) { %}
@@ -43,7 +43,7 @@
<div class="col-sm-2 col-xs-2 text-right"> <div class="col-sm-2 col-xs-2 text-right">
{%= doc.get_formatted("amount") %} {%= doc.get_formatted("amount") %}
<div class="small text-muted"> <div class="small text-muted">
{%= doc.get_formatted("incoming_rate") %} {%= doc.get_formatted("valuation_rate") %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -33,7 +33,7 @@ def get_product_list(search=None, start=0, limit=10):
for d in data: for d in data:
d.route = ((d.parent_website_route + "/") if d.parent_website_route else "") \ d.route = ((d.parent_website_route + "/") if d.parent_website_route else "") \
+ d.page_name + (d.page_name or "")
return [get_item_for_list_in_html(r) for r in data] return [get_item_for_list_in_html(r) for r in data]

View File

@@ -68,7 +68,7 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_filter": 1, "in_filter": 1,
"in_list_view": 0, "in_list_view": 0,
"label": "Pincode", "label": "Postal Code",
"permlevel": 0, "permlevel": 0,
"search_index": 1 "search_index": 1
}, },
@@ -101,7 +101,7 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Phone", "label": "Phone",
"permlevel": 0, "permlevel": 0,
"reqd": 1 "reqd": 0
}, },
{ {
"fieldname": "fax", "fieldname": "fax",
@@ -199,7 +199,7 @@
"icon": "icon-map-marker", "icon": "icon-map-marker",
"idx": 1, "idx": 1,
"in_dialog": 0, "in_dialog": 0,
"modified": "2015-06-01 06:42:18.331818", "modified": "2015-08-10 19:42:18.331819",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Utilities", "module": "Utilities",
"name": "Address", "name": "Address",

View File

@@ -237,7 +237,7 @@ def repost_all_stock_vouchers():
doc = frappe.get_doc(voucher_type, voucher_no) doc = frappe.get_doc(voucher_type, voucher_no)
if voucher_type=="Stock Entry" and doc.purpose in ["Manufacture", "Repack"]: if voucher_type=="Stock Entry" and doc.purpose in ["Manufacture", "Repack"]:
doc.get_stock_and_rate(force=1) doc.calculate_rate_and_amount(force=1)
elif voucher_type=="Purchase Receipt" and doc.is_subcontracted == "Yes": elif voucher_type=="Purchase Receipt" and doc.is_subcontracted == "Yes":
doc.validate() doc.validate()

View File

@@ -1,6 +1,6 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
version = "5.5.1" version = "5.6.0"
with open("requirements.txt", "r") as f: with open("requirements.txt", "r") as f:
install_requires = f.readlines() install_requires = f.readlines()

View File

@@ -29,5 +29,21 @@
For Sales / Purchase Return Enhancement <a href="https://github.com/frappe/erpnext/issues/3582">#3582</a> For Sales / Purchase Return Enhancement <a href="https://github.com/frappe/erpnext/issues/3582">#3582</a>
</td> </td>
</tr> </tr>
<tr>
<td style="width: 30%">
PT. Ridho Sribumi Sejahtera
</td>
<td>
For Additional Costs in Stock Entry <a href="https://github.com/frappe/erpnext/issues/3613">#3613</a>
</td>
</tr>
<tr>
<td style="width: 30%">
<a href="http://www.rigpl.com">Rohit Industries</a>
</td>
<td>
For Mandrill Integration <a href="https://github.com/frappe/erpnext/issues/3546">#3546</a>
</td>
</tr>
</tbody> </tbody>
</table> </table>