Merge branch 'develop'

This commit is contained in:
Anand Doshi
2015-08-04 16:12:22 +05:30
90 changed files with 63358 additions and 33991 deletions

View File

@@ -2,7 +2,7 @@
## Questions ## Questions
If you have questions on how to use ERPNext or want help in customization or debugging of your scripts, please post on https://discuss.frappe.io. This is only for bug reports and feature requests. If you have questions on how to use ERPNext or want help in customization or debugging of your scripts, please post on https://discuss.erpnext.com. This is only for bug reports and feature requests.
## Reporting issues ## Reporting issues

View File

@@ -10,7 +10,7 @@ ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a
- [User Guide](https://manual.erpnext.com) - [User Guide](https://manual.erpnext.com)
- [Getting Help](http://erpnext.org/getting-help.html) - [Getting Help](http://erpnext.org/getting-help.html)
- [Discussion Forum](https://discuss.frappe.io/) - [Discussion Forum](https://discuss.erpnext.com/)
--- ---

View File

@@ -1,2 +1,2 @@
from __future__ import unicode_literals from __future__ import unicode_literals
__version__ = '5.4.2' __version__ = '5.5.0'

View File

@@ -23,6 +23,7 @@ class Account(Document):
def validate(self): def validate(self):
self.validate_parent() self.validate_parent()
self.validate_root_details() self.validate_root_details()
self.set_root_and_report_type()
self.validate_mandatory() self.validate_mandatory()
self.validate_warehouse_account() self.validate_warehouse_account()
self.validate_frozen_accounts_modifier() self.validate_frozen_accounts_modifier()
@@ -32,7 +33,7 @@ class Account(Document):
"""Fetch Parent Details and validate parent account""" """Fetch Parent Details and validate parent account"""
if self.parent_account: if self.parent_account:
par = frappe.db.get_value("Account", self.parent_account, par = frappe.db.get_value("Account", self.parent_account,
["name", "is_group", "report_type", "root_type", "company"], as_dict=1) ["name", "is_group", "company"], as_dict=1)
if not par: if not par:
throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account)) throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
elif par.name == self.name: elif par.name == self.name:
@@ -43,11 +44,25 @@ class Account(Document):
throw(_("Account {0}: Parent account {1} does not belong to company: {2}") throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
.format(self.name, self.parent_account, self.company)) .format(self.name, self.parent_account, self.company))
def set_root_and_report_type(self):
if self.parent_account:
par = frappe.db.get_value("Account", self.parent_account, ["report_type", "root_type"], as_dict=1)
if par.report_type: if par.report_type:
self.report_type = par.report_type self.report_type = par.report_type
if par.root_type: if par.root_type:
self.root_type = par.root_type self.root_type = par.root_type
if self.is_group:
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
if db_value:
if self.report_type != db_value.report_type:
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
(self.report_type, self.lft, self.rgt))
if self.root_type != db_value.root_type:
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
(self.root_type, self.lft, self.rgt))
def validate_root_details(self): def validate_root_details(self):
# does not exists parent # does not exists parent
if frappe.db.exists("Account", self.name): if frappe.db.exists("Account", self.name):

View File

@@ -58,7 +58,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
filters: [ filters: [
[opts[1], opts[2], "=", jvd.party], [opts[1], opts[2], "=", jvd.party],
[opts[1], "docstatus", "=", 1], [opts[1], "docstatus", "=", 1],
[opts[1], "outstanding_amount", ">", 0] [opts[1], "outstanding_amount", "!=", 0]
] ]
}; };
}); });

View File

@@ -156,12 +156,10 @@ class JournalEntry(AccountsController):
.format(d.against_jv, dr_or_cr)) .format(d.against_jv, dr_or_cr))
def validate_against_sales_invoice(self): def validate_against_sales_invoice(self):
payment_against_voucher = self.validate_account_in_against_voucher("against_invoice", "Sales Invoice") self.validate_account_in_against_voucher("against_invoice", "Sales Invoice")
self.validate_against_invoice_fields("Sales Invoice", payment_against_voucher)
def validate_against_purchase_invoice(self): def validate_against_purchase_invoice(self):
payment_against_voucher = self.validate_account_in_against_voucher("against_voucher", "Purchase Invoice") self.validate_account_in_against_voucher("against_voucher", "Purchase Invoice")
self.validate_against_invoice_fields("Purchase Invoice", payment_against_voucher)
def validate_against_sales_order(self): def validate_against_sales_order(self):
payment_against_voucher = self.validate_account_in_against_voucher("against_sales_order", "Sales Order") payment_against_voucher = self.validate_account_in_against_voucher("against_sales_order", "Sales Order")
@@ -183,10 +181,10 @@ class JournalEntry(AccountsController):
if d.get(against_field): if d.get(against_field):
dr_or_cr = "credit" if against_field in ["against_invoice", "against_sales_order"] \ dr_or_cr = "credit" if against_field in ["against_invoice", "against_sales_order"] \
else "debit" else "debit"
if against_field in ["against_invoice", "against_sales_order"] and flt(d.debit) > 0: 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)) frappe.throw(_("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, doctype))
if against_field in ["against_voucher", "against_purchase_order"] and flt(d.credit) > 0: if against_field == "against_purchase_order" and flt(d.credit) > 0:
frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, doctype)) frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, doctype))
against_voucher = frappe.db.get_value(doctype, d.get(against_field), against_voucher = frappe.db.get_value(doctype, d.get(against_field),
@@ -555,18 +553,18 @@ def get_outstanding(args):
and ifnull(against_jv, '')=''""".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
if against_jv_amount > 0:
return {"credit": against_jv_amount}
else:
return {"debit": -1* against_jv_amount}
elif args.get("doctype") == "Sales Invoice":
return { return {
"credit": flt(frappe.db.get_value("Sales Invoice", args["docname"], "outstanding_amount")) ("credit" if against_jv_amount > 0 else "debit"): abs(against_jv_amount)
}
elif args.get("doctype") == "Sales Invoice":
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", args["docname"], "outstanding_amount"))
return {
("credit" if outstanding_amount > 0 else "debit"): abs(outstanding_amount)
} }
elif args.get("doctype") == "Purchase Invoice": elif args.get("doctype") == "Purchase Invoice":
outstanding_amount = flt(frappe.db.get_value("Purchase Invoice", args["docname"], "outstanding_amount"))
return { return {
"debit": flt(frappe.db.get_value("Purchase Invoice", args["docname"], "outstanding_amount")) ("debit" if outstanding_amount > 0 else "credit"): abs(outstanding_amount)
} }
@frappe.whitelist() @frappe.whitelist()

View File

@@ -192,9 +192,9 @@ class PaymentReconciliation(Document):
.format(p.idx, p.allocated_amount, p.amount)) .format(p.idx, p.allocated_amount, p.amount))
invoice_outstanding = unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number) invoice_outstanding = unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)
if abs(flt(p.allocated_amount) - invoice_outstanding) > 0.009: if flt(p.allocated_amount) - invoice_outstanding > 0.009:
frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}") frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}")
.format(p.idx, p.allocated_amount, unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number))) .format(p.idx, p.allocated_amount, invoice_outstanding))
if not invoices_to_reconcile: if not invoices_to_reconcile:
frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row")) frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row"))

View File

@@ -439,7 +439,7 @@ class SalesInvoice(SellingController):
if gl_entries: if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
# if POS and amount is written off, there's no outstanding and hence no need to update it # if POS and amount is written off, updating outstanding amt after posting all gl entries
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account) else "Yes" update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
@@ -447,7 +447,8 @@ class SalesInvoice(SellingController):
if update_outstanding == "No": if update_outstanding == "No":
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
update_outstanding_amt(self.debit_to, "Customer", self.customer, self.doctype, self.name) update_outstanding_amt(self.debit_to, "Customer", self.customer,
self.doctype, self.return_against if cint(self.is_return) else self.name)
if repost_future_gle and cint(self.update_stock) \ if repost_future_gle and cint(self.update_stock) \
and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):

View File

@@ -790,6 +790,58 @@ class TestSalesInvoice(unittest.TestCase):
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_discount_on_net_total(self):
si = frappe.copy_doc(test_records[2])
si.apply_discount_on = "Net Total"
si.discount_amount = 625
si.insert()
expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount",
"net_rate", "base_net_rate", "net_amount", "base_net_amount"],
"_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500, 25, 25, 250, 250],
"_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750, 75, 75, 375, 375],
}
# check if children are saved
self.assertEquals(len(si.get("items")),
len(expected_values)-1)
# check if item values are calculated
for d in si.get("items"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total
self.assertEquals(si.base_total, 1250)
self.assertEquals(si.total, 1250)
self.assertEquals(si.base_net_total, 625)
self.assertEquals(si.net_total, 625)
# check tax calculation
expected_values = {
"keys": ["tax_amount", "tax_amount_after_discount_amount",
"base_tax_amount_after_discount_amount"],
"_Test Account Shipping Charges - _TC": [100, 100, 100],
"_Test Account Customs Duty - _TC": [62.5, 62.5, 62.5],
"_Test Account Excise Duty - _TC": [70, 70, 70],
"_Test Account Education Cess - _TC": [1.4, 1.4, 1.4],
"_Test Account S&H Education Cess - _TC": [.7, 0.7, 0.7],
"_Test Account CST - _TC": [17.2, 17.2, 17.2],
"_Test Account VAT - _TC": [78.13, 78.13, 78.13],
"_Test Account Discount - _TC": [-95.49, -95.49, -95.49]
}
for d in si.get("taxes"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.total_taxes_and_charges, 234.44)
self.assertEquals(si.base_grand_total, 859.44)
self.assertEquals(si.grand_total, 859.44)
def create_sales_invoice(**args): def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice") si = frappe.new_doc("Sales Invoice")

View File

@@ -46,7 +46,7 @@ def validate_filters(filters):
def get_columns(filters): def get_columns(filters):
return [_("Journal Entry") + ":Link/Journal Entry:140", return [_("Journal Entry") + ":Link/Journal Entry:140",
_("Party Type") + ":Link/DocType:100", _("Party") + ":Dynamic Link/Party Type:140", _("Party Type") + "::100", _("Party") + ":Dynamic Link/Party Type:140",
_("Posting Date") + ":Date:100", _("Posting Date") + ":Date:100",
_("Against Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"), _("Against Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"),
_("Against Invoice Posting Date") + ":Date:130", _("Debit") + ":Currency:120", _("Credit") + ":Currency:120", _("Against Invoice Posting Date") + ":Date:130", _("Debit") + ":Currency:120", _("Credit") + ":Currency:120",

View File

@@ -0,0 +1,2 @@
- Automatically insert Price List Rate in Price List if added in transaction if permission exists and allowed from Stock Settings
- Product Bundle now allowed for all Items (stock or non-stock)

View File

@@ -11,6 +11,8 @@ 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")
class CustomerFrozen(frappe.ValidationError): pass class CustomerFrozen(frappe.ValidationError): pass
class AccountsController(TransactionBase): class AccountsController(TransactionBase):
@@ -142,7 +144,8 @@ class AccountsController(TransactionBase):
for fieldname, value in ret.items(): for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and \ if item.meta.get_field(fieldname) and \
item.get(fieldname) is None and value is not None: (item.get(fieldname) is None or fieldname in force_item_fields) \
and value is not None:
item.set(fieldname, value) item.set(fieldname, value)
if fieldname == "cost_center" and item.meta.get_field("cost_center") \ if fieldname == "cost_center" and item.meta.get_field("cost_center") \

View File

@@ -8,7 +8,6 @@ from frappe.utils import flt, get_datetime, format_datetime
class StockOverReturnError(frappe.ValidationError): pass class StockOverReturnError(frappe.ValidationError): pass
def validate_return(doc): def validate_return(doc):
if not doc.meta.get_field("is_return") or not doc.is_return: if not doc.meta.get_field("is_return") or not doc.is_return:
return return
@@ -50,13 +49,19 @@ def validate_return_against(doc):
.format(doc.return_against)) .format(doc.return_against))
def validate_returned_items(doc): def validate_returned_items(doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
valid_items = frappe._dict() valid_items = frappe._dict()
for d in frappe.db.sql("""select item_code, sum(qty) as qty, rate from `tab{0} Item`
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1): select_fields = "item_code, sum(qty) as qty, rate" if doc.doctype=="Purchase Invoice" \
else "item_code, sum(qty) as qty, rate, serial_no, batch_no"
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s
group by item_code""".format(select_fields, doc.doctype), doc.return_against, as_dict=1):
valid_items.setdefault(d.item_code, d) valid_items.setdefault(d.item_code, d)
if doc.doctype in ("Delivery Note", "Sales Invoice"): if doc.doctype in ("Delivery Note", "Sales Invoice"):
for d in frappe.db.sql("""select item_code, sum(qty) as qty from `tabPacked Item` for d in frappe.db.sql("""select item_code, sum(qty) as qty, serial_no, batch_no from `tabPacked Item`
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1): where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1):
valid_items.setdefault(d.item_code, d) valid_items.setdefault(d.item_code, d)
@@ -81,7 +86,19 @@ def validate_returned_items(doc):
elif ref.rate and flt(d.rate) != ref.rate: elif ref.rate and flt(d.rate) != ref.rate:
frappe.throw(_("Row # {0}: Rate must be same as {1} {2}") frappe.throw(_("Row # {0}: Rate must be same as {1} {2}")
.format(d.idx, doc.doctype, doc.return_against)) .format(d.idx, doc.doctype, doc.return_against))
elif ref.batch_no and d.batch_no != ref.batch_no:
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
.format(d.idx, doc.doctype, doc.return_against))
elif ref.serial_no:
if not d.serial_no:
frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
else:
serial_nos = get_serial_nos(d.serial_no)
ref_serial_nos = get_serial_nos(ref.serial_no)
for s in serial_nos:
if s not in ref_serial_nos:
frappe.throw(_("Row # {0}: Serial No {1} does not match with {2} {3}")
.format(d.idx, s, doc.doctype, doc.return_against))
items_returned = True items_returned = True
@@ -134,9 +151,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
}, },
doctype +" Item": { doctype +" Item": {
"doctype": doctype + " Item", "doctype": doctype + " Item",
"fields": { "field_map": {
"purchase_order": "purchase_order", "purchase_order": "purchase_order",
"purchase_receipt": "purchase_receipt" "purchase_receipt": "purchase_receipt",
"serial_no": "serial_no",
"batch_no": "batch_no"
}, },
"postprocess": update_item "postprocess": update_item
}, },

View File

@@ -234,7 +234,7 @@ class StockController(AccountsController):
if d['reserved_qty'] < 0 : if d['reserved_qty'] < 0 :
# Reduce reserved qty from reserved warehouse mentioned in so # Reduce reserved qty from reserved warehouse mentioned in so
if not d["reserved_warehouse"]: if not d["reserved_warehouse"]:
frappe.throw(_("Reserved Warehouse is missing in Sales Order")) frappe.throw(_("Delivery Warehouse is missing in Sales Order"))
args = { args = {
"item_code": d['item_code'], "item_code": d['item_code'],

View File

@@ -219,7 +219,7 @@ class calculate_taxes_and_totals(object):
# adjust Discount Amount loss in last tax iteration # adjust Discount Amount loss in last tax iteration
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
and self.doc.discount_amount: and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
self.adjust_discount_amount_loss(tax) self.adjust_discount_amount_loss(tax)
@@ -303,9 +303,9 @@ class calculate_taxes_and_totals(object):
for tax in self.doc.get("taxes"): for tax in self.doc.get("taxes"):
if tax.category in ["Valuation and Total", "Total"]: if tax.category in ["Valuation and Total", "Total"]:
if tax.add_deduct_tax == "Add": if tax.add_deduct_tax == "Add":
self.doc.taxes_and_charges_added += flt(tax.tax_amount) self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
else: else:
self.doc.taxes_and_charges_deducted += flt(tax.tax_amount) self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]) self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])

View File

@@ -34,10 +34,8 @@ class NewsletterList(Document):
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)
added += 1 added += 1
except Exception, e: except frappe.UniqueValidationError:
# already added, ignore pass
if e.args[0]!=1062:
raise
frappe.msgprint(_("{0} subscribers added").format(added)) frappe.msgprint(_("{0} subscribers added").format(added))

View File

@@ -27,7 +27,7 @@ blogs.
""" """
app_icon = "icon-th" app_icon = "icon-th"
app_color = "#e74c3c" app_color = "#e74c3c"
app_version = "5.4.2" app_version = "5.5.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

@@ -44,31 +44,35 @@ class ProductionPlanningTool(Document):
""" Pull sales orders which are pending to deliver based on criteria selected""" """ Pull sales orders which are pending to deliver based on criteria selected"""
so_filter = item_filter = "" so_filter = item_filter = ""
if self.from_date: if self.from_date:
so_filter += ' and so.transaction_date >= "' + self.from_date + '"' so_filter += " and so.transaction_date >= %(from_date)s"
if self.to_date: if self.to_date:
so_filter += ' and so.transaction_date <= "' + self.to_date + '"' so_filter += " and so.transaction_date <= %(to_date)s"
if self.customer: if self.customer:
so_filter += ' and so.customer = "' + self.customer + '"' so_filter += " and so.customer = %(customer)s"
if self.fg_item: if self.fg_item:
item_filter += ' and item.name = "' + self.fg_item + '"' item_filter += " and item.name = %(item)s"
open_so = frappe.db.sql(""" open_so = frappe.db.sql("""
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
from `tabSales Order` so, `tabSales Order Item` so_item from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent = so.name where so_item.parent = so.name
and so.docstatus = 1 and so.status != "Stopped" and so.docstatus = 1 and so.status != "Stopped"
and so.company = %s and so.company = %(company)s
and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0) %s and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0) {0}
and (exists (select name from `tabItem` item where item.name=so_item.item_code and (exists (select name from `tabItem` item where item.name=so_item.item_code
and (item.is_pro_applicable = 1 and (item.is_pro_applicable = 1 or item.is_sub_contracted_item = 1 {1}))
or item.is_sub_contracted_item = 1 %s)
or exists (select name from `tabPacked Item` pi or exists (select name from `tabPacked Item` pi
where pi.parent = so.name and pi.parent_item = so_item.item_code where pi.parent = so.name and pi.parent_item = so_item.item_code
and exists (select name from `tabItem` item where item.name=pi.item_code and exists (select name from `tabItem` item where item.name=pi.item_code
and (item.is_pro_applicable = 1 and (item.is_pro_applicable = 1 or item.is_sub_contracted_item = 1) {2})))
or item.is_sub_contracted_item = 1) %s))) """.format(so_filter, item_filter, item_filter), {
""" % ('%s', so_filter, item_filter, item_filter), self.company, as_dict=1) "from_date": self.from_date,
"to_date": self.to_date,
"customer": self.customer,
"item": self.fg_item,
"company": self.company
}, as_dict=1)
self.add_so_in_table(open_so) self.add_so_in_table(open_so)

View File

@@ -184,3 +184,7 @@ execute:frappe.delete_doc("Module Def", "Contacts")
erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items # 30-07-2015 erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items # 30-07-2015
execute:frappe.reload_doctype("Leave Type") execute:frappe.reload_doctype("Leave Type")
execute:frappe.db.sql("update `tabLeave Type` set include_holiday=0") execute:frappe.db.sql("update `tabLeave Type` set include_holiday=0")
erpnext.patches.v5_4.set_root_and_report_type
erpnext.patches.v5_4.notify_system_managers_regarding_wrong_tax_calculation
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")

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
def execute():
frappe.reload_doctype("Sales Invoice")
return_entries = frappe.get_list("Sales Invoice", filters={"is_return": 1, "docstatus": 1},
fields=["debit_to", "customer", "return_against"])
for d in return_entries:
update_outstanding_amt(d.debit_to, "Customer", d.customer, "Sales Invoice", d.return_against)

View File

@@ -0,0 +1,41 @@
# Copyright (c) 2013, 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.email import sendmail_to_system_managers
from frappe.utils import get_url_to_form
def execute():
wrong_records = []
for dt in ("Quotation", "Sales Order", "Delivery Note", "Sales Invoice",
"Purchase Order", "Purchase Receipt", "Purchase Invoice"):
records = frappe.db.sql_list("""select name from `tab{0}`
where apply_discount_on = 'Net Total' and ifnull(discount_amount, 0) != 0
and modified >= '2015-02-17' and docstatus=1""".format(dt))
if records:
records = [get_url_to_form(dt, d) for d in records]
wrong_records.append([dt, records])
if wrong_records:
content = """Dear System Manager,
Due to an error related to Discount Amount on Net Total, tax calculation might be wrong in the following records. We did not fix the tax amount automatically because it can corrupt the entries, so we request you to check these records and amend if you found the calculation wrong.
Please check following Entries:
%s
Regards,
Administrator""" % "\n".join([(d[0] + ": " + ", ".join(d[1])) for d in wrong_records])
try:
sendmail_to_system_managers("[Important] [ERPNext] Tax calculation might be wrong, please check.", content)
except:
pass
print "="*50
print content
print "="*50

View File

@@ -0,0 +1,12 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
roots = frappe.db.sql("""select lft, rgt, report_type, root_type
from `tabAccount` where ifnull(parent_account, '')=''""", as_dict=1)
for d in roots:
frappe.db.sql("update `tabAccount` set report_type=%s, root_type=%s where lft > %s and rgt < %s",
(d.report_type, d.root_type, d.lft, d.rgt))

View File

@@ -256,7 +256,8 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
me.round_off_totals(tax); me.round_off_totals(tax);
// adjust Discount Amount loss in last tax iteration // adjust Discount Amount loss in last tax iteration
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied && me.frm.doc.apply_discount_on == "Grand Total") if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied
&& me.frm.doc.apply_discount_on == "Grand Total" && me.frm.doc.discount_amount)
me.adjust_discount_amount_loss(tax); me.adjust_discount_amount_loss(tax);
} }
}); });
@@ -365,9 +366,9 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
$.each(this.frm.doc["taxes"] || [], function(i, tax) { $.each(this.frm.doc["taxes"] || [], function(i, tax) {
if (in_list(["Valuation and Total", "Total"], tax.category)) { if (in_list(["Valuation and Total", "Total"], tax.category)) {
if(tax.add_deduct_tax == "Add") { if(tax.add_deduct_tax == "Add") {
me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount); me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount);
} else { } else {
me.frm.doc.taxes_and_charges_deducted += flt(tax.tax_amount); me.frm.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount);
} }
} }
}) })

View File

@@ -218,9 +218,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
item.serial_no += sr_no[x] + '\n'; item.serial_no += sr_no[x] + '\n';
refresh_field("serial_no", item.name, item.parentfield); refresh_field("serial_no", item.name, item.parentfield);
if(!doc.is_return) {
frappe.model.set_value(item.doctype, item.name, "qty", sr_no.length); frappe.model.set_value(item.doctype, item.name, "qty", sr_no.length);
} }
} }
}
}, },
validate: function() { validate: function() {

View File

@@ -83,7 +83,7 @@ $.extend(erpnext, {
return { return {
filters: { filters: {
item_code:grid_row.doc.item_code , item_code:grid_row.doc.item_code ,
warehouse:grid_row.doc.warehouse warehouse:cur_frm.doc.is_return ? null : grid_row.doc.warehouse
} }
} }
} }

View File

@@ -1,21 +1,41 @@
{ {
"allow_copy": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0,
"creation": "2013-06-20 11:53:21", "creation": "2013-06-20 11:53:21",
"custom": 0,
"description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials", "description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Master", "document_type": "",
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"fieldname": "basic_section", "fieldname": "basic_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "", "label": "",
"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
}, },
{ {
"description": "The Item that represents the Package. This Item must have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\"", "allow_on_submit": 0,
"description": "",
"fieldname": "new_item_code", "fieldname": "new_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Parent Item", "label": "Parent Item",
"no_copy": 1, "no_copy": 1,
@@ -23,30 +43,67 @@
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Item", "options": "Item",
"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,
"description": "List items that form the package.", "description": "List items that form the package.",
"fieldname": "item_section", "fieldname": "item_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "", "label": "",
"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": "items", "fieldname": "items",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Items", "label": "Items",
"no_copy": 0,
"oldfieldname": "sales_bom_items", "oldfieldname": "sales_bom_items",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Product Bundle Item", "options": "Product Bundle Item",
"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
} }
], ],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-sitemap", "icon": "icon-sitemap",
"idx": 1, "idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"modified": "2015-07-13 05:28:28.140327", "issingle": 0,
"istable": 0,
"modified": "2015-08-03 11:23:26.263254",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Product Bundle", "name": "Product Bundle",
@@ -54,14 +111,20 @@
"permissions": [ "permissions": [
{ {
"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,
"report": 1, "report": 1,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
@@ -69,31 +132,44 @@
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 1,
"cancel": 0,
"create": 0, "create": 0,
"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": "Stock User", "role": "Stock User",
"set_user_permissions": 0,
"share": 0,
"submit": 0, "submit": 0,
"write": 0 "write": 0
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 1,
"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,
"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
} }
] ],
"read_only": 0,
"read_only_onload": 0
} }

View File

@@ -14,15 +14,13 @@ class ProductBundle(Document):
def validate(self): def validate(self):
self.validate_main_item() self.validate_main_item()
from erpnext.utilities.transaction_base import validate_uom_is_integer from erpnext.utilities.transaction_base import validate_uom_is_integer
validate_uom_is_integer(self, "uom", "qty") validate_uom_is_integer(self, "uom", "qty")
def validate_main_item(self): def validate_main_item(self):
"""main item must have Is Stock Item as No and Is Sales Item as Yes""" """Validates, main Item is not a stock item"""
if not frappe.db.sql("""select name from tabItem where name=%s and if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"):
is_stock_item = 0 and is_sales_item = 1""", self.new_item_code): frappe.throw(_("Parent Item {0} must not be a Stock Item").format(self.new_item_code))
frappe.throw(_("Parent Item {0} must be not Stock Item and must be a Sales Item").format(self.new_item_code))
def get_item_details(self, name): def get_item_details(self, name):
det = frappe.db.sql("""select description, stock_uom from `tabItem` det = frappe.db.sql("""select description, stock_uom from `tabItem`
@@ -36,8 +34,7 @@ def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
return frappe.db.sql("""select name, item_name, description from tabItem return frappe.db.sql("""select name, item_name, description from tabItem
where is_stock_item=0 and is_sales_item=1 where is_stock_item=0 and name not in (select name from `tabProduct Bundle`)
and name not in (select name from `tabProduct Bundle`) and %s like %s and %s like %s %s limit %s, %s""" % (searchfield, "%s",
%s limit %s, %s""" % (searchfield, "%s",
get_match_cond(doctype),"%s", "%s"), get_match_cond(doctype),"%s", "%s"),
("%%%s%%" % txt, start, page_len)) ("%%%s%%" % txt, start, page_len))

View File

@@ -6,3 +6,19 @@ from __future__ import unicode_literals
import frappe import frappe
test_records = frappe.get_test_records('Product Bundle') test_records = frappe.get_test_records('Product Bundle')
def make_product_bundle(parent, items):
if frappe.db.exists("Product Bundle", parent):
return frappe.get_doc("Product Bundle", parent)
product_bundle = frappe.get_doc({
"doctype": "Product Bundle",
"new_item_code": parent
})
for item in items:
product_bundle.append("items", {"item_code": item, "qty": 1})
product_bundle.insert()
return product_bundle

View File

@@ -15,6 +15,8 @@ form_grid_templates = {
"items": "templates/form_grid/item_grid.html" "items": "templates/form_grid/item_grid.html"
} }
class WarehouseRequired(frappe.ValidationError): pass
class SalesOrder(SellingController): class SalesOrder(SellingController):
def validate_mandatory(self): def validate_mandatory(self):
# validate transaction date v/s delivery date # validate transaction date v/s delivery date
@@ -40,8 +42,10 @@ class SalesOrder(SellingController):
check_list.append(cstr(d.item_code)) check_list.append(cstr(d.item_code))
if (frappe.db.get_value("Item", d.item_code, "is_stock_item")==1 or if (frappe.db.get_value("Item", d.item_code, "is_stock_item")==1 or
self.has_product_bundle(d.item_code)) and not d.warehouse: (self.has_product_bundle(d.item_code) and self.product_bundle_has_stock_item(d.item_code))) \
frappe.throw(_("Reserved warehouse required for stock item {0}").format(d.item_code)) and not d.warehouse:
frappe.throw(_("Delivery warehouse required for stock item {0}").format(d.item_code),
WarehouseRequired)
# used for production plan # used for production plan
d.transaction_date = self.transaction_date d.transaction_date = self.transaction_date
@@ -53,6 +57,12 @@ class SalesOrder(SellingController):
if len(unique_chk_list) != len(check_list): if len(unique_chk_list) != len(check_list):
frappe.msgprint(_("Warning: Same item has been entered multiple times.")) frappe.msgprint(_("Warning: Same item has been entered multiple times."))
def product_bundle_has_stock_item(self, product_bundle):
"""Returns true if product bundle has stock item"""
ret = len(frappe.db.sql("""select i.name from tabItem i, `tabProduct Bundle Item` pbi
where pbi.parent = %s and pbi.item_code = i.name and i.is_stock_item = 1""", product_bundle))
return ret
def validate_sales_mntc_quotation(self): def validate_sales_mntc_quotation(self):
for d in self.get('items'): for d in self.get('items'):
if d.prevdoc_docname: if d.prevdoc_docname:

View File

@@ -6,7 +6,7 @@ from frappe.utils import flt, add_days
import frappe.permissions import frappe.permissions
import unittest import unittest
from erpnext.selling.doctype.sales_order.sales_order \ from erpnext.selling.doctype.sales_order.sales_order \
import make_material_request, make_delivery_note, make_sales_invoice import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
class TestSalesOrder(unittest.TestCase): class TestSalesOrder(unittest.TestCase):
def tearDown(self): def tearDown(self):
@@ -224,6 +224,66 @@ class TestSalesOrder(unittest.TestCase):
self.assertRaises(frappe.CancelledLinkError, dn.submit) self.assertRaises(frappe.CancelledLinkError, dn.submit)
def test_service_type_product_bundle(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
make_item("_Test Service Product Bundle", {"is_stock_item": 0, "is_sales_item": 1})
make_item("_Test Service Product Bundle Item 1", {"is_stock_item": 0, "is_sales_item": 1})
make_item("_Test Service Product Bundle Item 2", {"is_stock_item": 0, "is_sales_item": 1})
make_product_bundle("_Test Service Product Bundle",
["_Test Service Product Bundle Item 1", "_Test Service Product Bundle Item 2"])
so = make_sales_order(item_code = "_Test Service Product Bundle", warehouse=None)
self.assertTrue("_Test Service Product Bundle Item 1" in [d.item_code for d in so.packed_items])
self.assertTrue("_Test Service Product Bundle Item 2" in [d.item_code for d in so.packed_items])
def test_mix_type_product_bundle(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
make_item("_Test Mix Product Bundle", {"is_stock_item": 0, "is_sales_item": 1})
make_item("_Test Mix Product Bundle Item 1", {"is_stock_item": 1, "is_sales_item": 1})
make_item("_Test Mix Product Bundle Item 2", {"is_stock_item": 0, "is_sales_item": 1})
make_product_bundle("_Test Mix Product Bundle",
["_Test Mix Product Bundle Item 1", "_Test Mix Product Bundle Item 2"])
self.assertRaises(WarehouseRequired, make_sales_order, item_code = "_Test Mix Product Bundle", warehouse="")
def test_auto_insert_price(self):
from erpnext.stock.doctype.item.test_item import make_item
make_item("_Test Item for Auto Price List", {"is_stock_item": 0, "is_sales_item": 1})
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1)
item_price = frappe.db.get_value("Item Price", {"price_list": "_Test Price List",
"item_code": "_Test Item for Auto Price List"})
if item_price:
frappe.delete_doc("Item Price", item_price)
make_sales_order(item_code = "_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100)
self.assertEquals(frappe.db.get_value("Item Price",
{"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"}, "price_list_rate"), 100)
# do not update price list
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0)
item_price = frappe.db.get_value("Item Price", {"price_list": "_Test Price List",
"item_code": "_Test Item for Auto Price List"})
if item_price:
frappe.delete_doc("Item Price", item_price)
make_sales_order(item_code = "_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100)
self.assertEquals(frappe.db.get_value("Item Price",
{"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"}, "price_list_rate"), None)
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1)
def make_sales_order(**args): def make_sales_order(**args):
so = frappe.new_doc("Sales Order") so = frappe.new_doc("Sales Order")
args = frappe._dict(args) args = frappe._dict(args)
@@ -234,14 +294,20 @@ def make_sales_order(**args):
so.customer = args.customer or "_Test Customer" so.customer = args.customer or "_Test Customer"
so.delivery_date = add_days(so.transaction_date, 10) so.delivery_date = add_days(so.transaction_date, 10)
so.currency = args.currency or "INR" so.currency = args.currency or "INR"
if args.selling_price_list:
so.selling_price_list = args.selling_price_list
if "warehouse" not in args:
args.warehouse = "_Test Warehouse - _TC"
so.append("items", { so.append("items", {
"item_code": args.item or args.item_code or "_Test Item", "item_code": args.item or args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse,
"qty": args.qty or 10, "qty": args.qty or 10,
"rate": args.rate or 100, "rate": args.rate or 100,
"conversion_factor": 1.0, "conversion_factor": 1.0,
}) })
if not args.do_not_save: if not args.do_not_save:
so.insert() so.insert()
if not args.do_not_submit: if not args.do_not_submit:

View File

@@ -1,103 +1,250 @@
{ {
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2013-06-25 10:25:16", "creation": "2013-06-25 10:25:16",
"custom": 0,
"description": "Settings for Selling Module", "description": "Settings for Selling Module",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Other", "document_type": "Other",
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"default": "Customer Name", "default": "Customer Name",
"fieldname": "cust_master_name", "fieldname": "cust_master_name",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Customer Naming By", "label": "Customer Naming By",
"no_copy": 0,
"options": "Customer Name\nNaming Series", "options": "Customer Name\nNaming Series",
"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": "campaign_naming_by", "fieldname": "campaign_naming_by",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Campaign Naming By", "label": "Campaign Naming By",
"no_copy": 0,
"options": "Campaign Name\nNaming Series", "options": "Campaign Name\nNaming Series",
"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,
"description": "", "description": "",
"fieldname": "customer_group", "fieldname": "customer_group",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Default Customer Group", "label": "Default Customer Group",
"no_copy": 0,
"options": "Customer Group", "options": "Customer 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,
"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": "Default Territory", "label": "Default Territory",
"no_copy": 0,
"options": "Territory", "options": "Territory",
"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": "selling_price_list", "fieldname": "selling_price_list",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Default Price List", "label": "Default 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": "column_break_5", "fieldname": "column_break_5",
"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": "so_required", "fieldname": "so_required",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sales Order Required", "label": "Sales Order Required",
"no_copy": 0,
"options": "No\nYes", "options": "No\nYes",
"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": "dn_required", "fieldname": "dn_required",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Delivery Note Required", "label": "Delivery Note Required",
"no_copy": 0,
"options": "No\nYes", "options": "No\nYes",
"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": "maintain_same_sales_rate", "fieldname": "maintain_same_sales_rate",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Maintain Same Rate Throughout Sales Cycle", "label": "Maintain Same Rate Throughout Sales Cycle",
"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": "editable_price_list_rate", "fieldname": "editable_price_list_rate",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Allow user to edit Price List Rate in transactions", "label": "Allow user to edit Price List Rate in transactions",
"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
} }
], ],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1, "issingle": 1,
"modified": "2015-02-05 05:11:46.384538", "istable": 0,
"modified": "2015-08-03 12:59:51.829458",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Selling Settings", "name": "Selling Settings",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1, "create": 1,
"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": 0,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
] ],
"read_only": 0,
"read_only_onload": 0
} }

View File

@@ -70,7 +70,8 @@ class Company(Document):
frappe.clear_cache() frappe.clear_cache()
def install_country_fixtures(self): def install_country_fixtures(self):
if os.path.exists(os.path.join(os.path.dirname(__file__), "fixtures", self.country.lower())): path = os.path.join(os.path.dirname(__file__), "fixtures", self.country.lower())
if os.path.exists(path.encode("utf-8")):
frappe.get_attr("erpnext.setup.doctype.company.fixtures.{0}.install".format(self.country.lower()))(self) frappe.get_attr("erpnext.setup.doctype.company.fixtures.{0}.install".format(self.country.lower()))(self)
def create_default_warehouses(self): def create_default_warehouses(self):

View File

@@ -220,6 +220,7 @@ def set_defaults(args):
stock_settings.valuation_method = "FIFO" stock_settings.valuation_method = "FIFO"
stock_settings.stock_uom = _("Nos") stock_settings.stock_uom = _("Nos")
stock_settings.auto_indent = 1 stock_settings.auto_indent = 1
stock_settings.auto_insert_price_list_rate_if_missing = 1
stock_settings.save() stock_settings.save()
selling_settings = frappe.get_doc("Selling Settings") selling_settings = frappe.get_doc("Selling Settings")

View File

@@ -56,6 +56,9 @@ def before_tests():
frappe.db.sql("delete from `tabLeave Application`") frappe.db.sql("delete from `tabLeave Application`")
frappe.db.sql("delete from `tabSalary Slip`") frappe.db.sql("delete from `tabSalary Slip`")
frappe.db.sql("delete from `tabItem Price`") frappe.db.sql("delete from `tabItem Price`")
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0)
frappe.db.commit() frappe.db.commit()
@frappe.whitelist() @frappe.whitelist()

File diff suppressed because it is too large Load Diff

View File

@@ -108,9 +108,13 @@ class Item(WebsiteGenerator):
def check_stock_uom_with_bin(self): def check_stock_uom_with_bin(self):
if not self.get("__islocal"): if not self.get("__islocal"):
if self.stock_uom == frappe.db.get_value("Item", self.name, "stock_uom"):
return
matched=True matched=True
ref_uom = frappe.db.get_value("Stock Ledger Entry", ref_uom = frappe.db.get_value("Stock Ledger Entry",
{"item_code": self.name}, "stock_uom") {"item_code": self.name}, "stock_uom")
if ref_uom: if ref_uom:
if cstr(ref_uom) != cstr(self.stock_uom): if cstr(ref_uom) != cstr(self.stock_uom):
matched = False matched = False
@@ -128,7 +132,7 @@ class Item(WebsiteGenerator):
(self.stock_uom, self.name)) (self.stock_uom, self.name))
if not matched: if not matched:
frappe.throw(_("Default Unit of Measure can not 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.")) 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)

View File

@@ -12,6 +12,29 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
test_ignore = ["BOM"] test_ignore = ["BOM"]
test_dependencies = ["Warehouse"] test_dependencies = ["Warehouse"]
def make_item(item_code, properties=None):
if frappe.db.exists("Item", item_code):
return frappe.get_doc("Item", item_code)
item = frappe.get_doc({
"doctype": "Item",
"item_code": item_code,
"item_name": item_code,
"description": item_code,
"item_group": "Products"
})
if properties:
item.update(properties)
if item.is_stock_item and not item.default_warehouse:
item.default_warehouse = "_Test Warehouse - _TC"
item.insert()
return item
class TestItem(unittest.TestCase): class TestItem(unittest.TestCase):
def get_item(self, idx): def get_item(self, idx):
item_code = test_records[idx].get("item_code") item_code = test_records[idx].get("item_code")

View File

@@ -115,7 +115,7 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
d.get_input("fetch").on("click", function() { d.get_input("fetch").on("click", function() {
var values = d.get_values(); var values = d.get_values();
if(!values) return; if(!values) return;
values["company"] = cur_frm.doc.company;
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items",
args: values, args: values,

View File

@@ -93,7 +93,7 @@ class StockEntry(StockController):
frappe.throw(_("{0} is not a stock Item").format(item.item_code)) frappe.throw(_("{0} is not a stock Item").format(item.item_code))
item_details = self.get_item_details(frappe._dict({"item_code": item.item_code, item_details = self.get_item_details(frappe._dict({"item_code": item.item_code,
"company": self.company, "project_name": self.project_name})) "company": self.company, "project_name": self.project_name, "uom": item.uom}), for_update=True)
for f in ("uom", "stock_uom", "description", "item_name", "expense_account", for f in ("uom", "stock_uom", "description", "item_name", "expense_account",
"cost_center", "conversion_factor"): "cost_center", "conversion_factor"):
@@ -419,7 +419,7 @@ class StockEntry(StockController):
"planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty) "planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty)
}) })
def get_item_details(self, args=None): def get_item_details(self, args=None, for_update=False):
item = frappe.db.sql("""select stock_uom, description, image, item_name, item = frappe.db.sql("""select stock_uom, description, image, item_name,
expense_account, buying_cost_center, item_group from `tabItem` expense_account, buying_cost_center, item_group from `tabItem`
where name = %s and (ifnull(end_of_life,'0000-00-00')='0000-00-00' or end_of_life > now())""", where name = %s and (ifnull(end_of_life,'0000-00-00')='0000-00-00' or end_of_life > now())""",
@@ -450,14 +450,22 @@ class StockEntry(StockController):
if not ret[d[1]] or (company and self.company != company): if not ret[d[1]] or (company and self.company != company):
ret[d[1]] = frappe.db.get_value("Company", self.company, d[2]) if d[2] else None ret[d[1]] = frappe.db.get_value("Company", self.company, d[2]) if d[2] else None
# update uom
if args.get("uom") and for_update:
ret.update(self.get_uom_details(args))
if not ret["expense_account"]: if not ret["expense_account"]:
ret["expense_account"] = frappe.db.get_value("Company", self.company, "stock_adjustment_account") ret["expense_account"] = frappe.db.get_value("Company", self.company, "stock_adjustment_account")
stock_and_rate = args.get('warehouse') and self.get_warehouse_details(args) or {} stock_and_rate = args.get('warehouse') and self.get_warehouse_details(args) or {}
ret.update(stock_and_rate) ret.update(stock_and_rate)
return ret return ret
def get_uom_details(self, args): def get_uom_details(self, args):
"""Returns dict `{"conversion_factor": [value], "transfer_qty": qty * [value]}`
:param args: dict with `item_code`, `uom` and `qty`"""
conversion_factor = get_conversion_factor(args.get("item_code"), args.get("uom")).get("conversion_factor") conversion_factor = get_conversion_factor(args.get("item_code"), args.get("uom")).get("conversion_factor")
if not conversion_factor: if not conversion_factor:

View File

@@ -82,7 +82,6 @@ class StockLedgerEntry(Document):
frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code), frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
ItemTemplateCannotHaveStock) ItemTemplateCannotHaveStock)
if not self.stock_uom:
self.stock_uom = item_det.stock_uom self.stock_uom = item_det.stock_uom
def check_stock_frozen_date(self): def check_stock_frozen_date(self):

View File

@@ -1,123 +1,343 @@
{ {
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2013-06-24 16:37:54", "creation": "2013-06-24 16:37:54",
"custom": 0,
"description": "Settings", "description": "Settings",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"default": "Item Code", "default": "Item Code",
"fieldname": "item_naming_by", "fieldname": "item_naming_by",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Item Naming By", "label": "Item Naming By",
"no_copy": 0,
"options": "Item Code\nNaming Series", "options": "Item Code\nNaming Series",
"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,
"description": "", "description": "",
"fieldname": "item_group", "fieldname": "item_group",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Default Item Group", "label": "Default Item Group",
"no_copy": 0,
"options": "Item Group", "options": "Item 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": "stock_uom", "fieldname": "stock_uom",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Default Stock UOM", "label": "Default Stock UOM",
"no_copy": 0,
"options": "UOM", "options": "UOM",
"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": "auto_insert_price_list_rate_if_missing",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Auto insert Price List rate if missing",
"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": "column_break_4", "fieldname": "column_break_4",
"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": "valuation_method", "fieldname": "valuation_method",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Default Valuation Method", "label": "Default Valuation Method",
"no_copy": 0,
"options": "FIFO\nMoving Average", "options": "FIFO\nMoving Average",
"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,
"description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.",
"fieldname": "tolerance", "fieldname": "tolerance",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Allowance Percent", "label": "Allowance Percent",
"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": "allow_negative_stock", "fieldname": "allow_negative_stock",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Allow Negative Stock", "label": "Allow Negative Stock",
"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": "auto_material_request", "fieldname": "auto_material_request",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Auto Material Request", "label": "Auto Material Request",
"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": "auto_indent", "fieldname": "auto_indent",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Raise Material Request when stock reaches re-order level", "label": "Raise Material Request when stock reaches re-order level",
"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": "reorder_email_notify", "fieldname": "reorder_email_notify",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Notify by Email on creation of automatic Material Request", "label": "Notify by Email on creation of automatic Material Request",
"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": "freeze_stock_entries", "fieldname": "freeze_stock_entries",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Freeze Stock Entries", "label": "Freeze Stock Entries",
"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": "stock_frozen_upto", "fieldname": "stock_frozen_upto",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Stock Frozen Upto", "label": "Stock Frozen Upto",
"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": "stock_frozen_upto_days", "fieldname": "stock_frozen_upto_days",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Freeze Stocks Older Than [Days]", "label": "Freeze Stocks Older Than [Days]",
"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": "stock_auth_role", "fieldname": "stock_auth_role",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Role Allowed to edit frozen stock", "label": "Role Allowed to edit frozen stock",
"no_copy": 0,
"options": "Role", "options": "Role",
"permlevel": 0 "permlevel": 0,
"print_hide": 0,
"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-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1, "issingle": 1,
"modified": "2015-07-13 05:28:23.839277", "istable": 0,
"modified": "2015-08-03 13:00:36.082986",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Settings", "name": "Stock Settings",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1, "create": 1,
"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": 0,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
] ],
"read_only": 0,
"read_only_onload": 0
} }

View File

@@ -214,6 +214,8 @@ def get_price_list_rate(args, item_doc, out):
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
if not price_list_rate: if not price_list_rate:
if args.price_list and args.rate:
insert_item_price(args)
return {} return {}
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \ out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
@@ -224,6 +226,22 @@ def get_price_list_rate(args, item_doc, out):
out.update(get_last_purchase_details(item_doc.name, out.update(get_last_purchase_details(item_doc.name,
args.parent, args.conversion_rate)) args.parent, args.conversion_rate))
def insert_item_price(args):
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
if frappe.db.get_value("Price List", args.price_list, "currency") == args.currency \
and cint(frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")):
if frappe.has_permission("Item Price", "write"):
item_price = frappe.get_doc({
"doctype": "Item Price",
"price_list": args.price_list,
"item_code": args.item_code,
"currency": args.currency,
"price_list_rate": args.rate
})
item_price.insert()
frappe.msgprint("Item Price added for {0} in Price List {1}".format(args.item_code,
args.price_list))
def get_price_list_rate_for(args, item_code): def get_price_list_rate_for(args, item_code):
return frappe.db.get_value("Item Price", return frappe.db.get_value("Item Price",
{"price_list": args.price_list, "item_code": item_code}, "price_list_rate") {"price_list": args.price_list, "item_code": item_code}, "price_list_rate")

View File

@@ -300,7 +300,7 @@ class update_entries_after(object):
# select first batch or the batch with same rate # select first batch or the batch with same rate
batch = self.stock_queue[index] batch = self.stock_queue[index]
if batch[0]:
if qty_to_pop >= batch[0]: if qty_to_pop >= batch[0]:
# consume current batch # consume current batch
qty_to_pop = qty_to_pop - batch[0] qty_to_pop = qty_to_pop - batch[0]

View File

@@ -133,6 +133,7 @@ def get_fifo_rate(previous_stock_queue, qty):
qty_to_pop = abs(qty) qty_to_pop = abs(qty)
while qty_to_pop and previous_stock_queue: while qty_to_pop and previous_stock_queue:
batch = previous_stock_queue[0] batch = previous_stock_queue[0]
if batch[0]:
if 0 < batch[0] <= qty_to_pop: if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0 # if batch qty > 0
# not enough or exactly same qty in current batch, clear batch # not enough or exactly same qty in current batch, clear batch

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3904
erpnext/translations/fi.csv Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3480
erpnext/translations/km.csv Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3901
erpnext/translations/mk.csv Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3903
erpnext/translations/my.csv Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3904
erpnext/translations/no.csv Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3902
erpnext/translations/sq.csv Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3904
erpnext/translations/sv.csv Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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