Merge branch 'develop'

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

View File

@ -2,7 +2,7 @@
## 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

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)
- [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
__version__ = '5.4.2'
__version__ = '5.5.0'

View File

@ -23,6 +23,7 @@ class Account(Document):
def validate(self):
self.validate_parent()
self.validate_root_details()
self.set_root_and_report_type()
self.validate_mandatory()
self.validate_warehouse_account()
self.validate_frozen_accounts_modifier()
@ -32,7 +33,7 @@ class Account(Document):
"""Fetch Parent Details and validate parent account"""
if 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:
throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
elif par.name == self.name:
@ -43,10 +44,24 @@ class Account(Document):
throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
.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:
self.report_type = par.report_type
if 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):
# does not exists parent

View File

@ -58,7 +58,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
filters: [
[opts[1], opts[2], "=", jvd.party],
[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))
def validate_against_sales_invoice(self):
payment_against_voucher = self.validate_account_in_against_voucher("against_invoice", "Sales Invoice")
self.validate_against_invoice_fields("Sales Invoice", payment_against_voucher)
self.validate_account_in_against_voucher("against_invoice", "Sales Invoice")
def validate_against_purchase_invoice(self):
payment_against_voucher = self.validate_account_in_against_voucher("against_voucher", "Purchase Invoice")
self.validate_against_invoice_fields("Purchase Invoice", payment_against_voucher)
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")
@ -183,10 +181,10 @@ class JournalEntry(AccountsController):
if d.get(against_field):
dr_or_cr = "credit" if against_field in ["against_invoice", "against_sales_order"] \
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))
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))
against_voucher = frappe.db.get_value(doctype, d.get(against_field),
@ -210,7 +208,7 @@ class JournalEntry(AccountsController):
def validate_against_invoice_fields(self, doctype, payment_against_voucher):
for voucher_no, payment_list in payment_against_voucher.items():
voucher_properties = frappe.db.get_value(doctype, voucher_no,
voucher_properties = frappe.db.get_value(doctype, voucher_no,
["docstatus", "outstanding_amount"])
if voucher_properties[0] != 1:
@ -555,18 +553,18 @@ def get_outstanding(args):
and ifnull(against_jv, '')=''""".format(condition), args)
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 {
"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":
outstanding_amount = flt(frappe.db.get_value("Purchase Invoice", args["docname"], "outstanding_amount"))
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()

View File

@ -192,9 +192,9 @@ class PaymentReconciliation(Document):
.format(p.idx, p.allocated_amount, p.amount))
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}")
.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:
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:
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"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
@ -447,7 +447,8 @@ class SalesInvoice(SellingController):
if update_outstanding == "No":
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) \
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)
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):
si = frappe.new_doc("Sales Invoice")

View File

@ -10,7 +10,7 @@ from frappe.utils import flt
def execute(filters=None):
if not filters: filters = {}
validate_filters(filters)
columns = get_columns(filters)
entries = get_entries(filters)
invoice_posting_date_map = get_invoice_posting_date_map(filters)
@ -37,7 +37,7 @@ def execute(filters=None):
data.append(row)
return columns, data
def validate_filters(filters):
if (filters.get("payment_type") == "Incoming" and filters.get("party_type") == "Supplier") or \
(filters.get("payment_type") == "Outgoing" and filters.get("party_type") == "Customer"):
@ -45,9 +45,9 @@ def validate_filters(filters):
.format(filters.payment_type, filters.party_type))
def get_columns(filters):
return [_("Journal Entry") + ":Link/Journal Entry:140",
_("Party Type") + ":Link/DocType:100", _("Party") + ":Dynamic Link/Party Type:140",
_("Posting Date") + ":Date:100",
return [_("Journal Entry") + ":Link/Journal Entry:140",
_("Party Type") + "::100", _("Party") + ":Dynamic Link/Party Type:140",
_("Posting Date") + ":Date:100",
_("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",
_("Reference No") + "::100", _("Reference Date") + ":Date:100", _("Remarks") + "::150", _("Age") +":Int:40",
@ -62,7 +62,7 @@ def get_conditions(filters):
filters["party_type"] = "Supplier"
else:
filters["party_type"] = "Customer"
if filters.get("party_type"):
conditions.append("jvd.party_type=%(party_type)s")

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.sales_and_purchase_return import validate_return
force_item_fields = ("item_name", "item_group", "barcode", "brand", "stock_uom")
class CustomerFrozen(frappe.ValidationError): pass
class AccountsController(TransactionBase):
@ -18,12 +20,12 @@ class AccountsController(TransactionBase):
if self.get("_action") and self._action != "update_after_submit":
self.set_missing_values(for_validate=True)
self.validate_date_with_fiscal_year()
if self.meta.get_field("currency"):
self.calculate_taxes_and_totals()
if not self.meta.get_field("is_return") or not self.is_return:
self.validate_value("base_grand_total", ">=", 0)
validate_return(self)
self.set_total_in_words()
@ -35,7 +37,7 @@ class AccountsController(TransactionBase):
if self.meta.get_field("taxes_and_charges"):
self.validate_enabled_taxes_and_charges()
self.validate_party()
def on_submit(self):
@ -86,7 +88,7 @@ class AccountsController(TransactionBase):
if self.doctype == "Sales Invoice":
if not self.due_date:
frappe.throw(_("Due Date is mandatory"))
validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company)
elif self.doctype == "Purchase Invoice":
validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company)
@ -142,7 +144,8 @@ class AccountsController(TransactionBase):
for fieldname, value in ret.items():
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)
if fieldname == "cost_center" and item.meta.get_field("cost_center") \
@ -349,14 +352,14 @@ class AccountsController(TransactionBase):
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if frozen_accounts_modifier in frappe.get_roles():
return
party_type = None
if self.meta.get_field("customer"):
party_type = 'Customer'
elif self.meta.get_field("supplier"):
party_type = 'Supplier'
if party_type:
party = self.get(party_type.lower())
if frappe.db.get_value(party_type, party, "is_frozen"):

View File

@ -8,7 +8,6 @@ from frappe.utils import flt, get_datetime, format_datetime
class StockOverReturnError(frappe.ValidationError): pass
def validate_return(doc):
if not doc.meta.get_field("is_return") or not doc.is_return:
return
@ -50,13 +49,19 @@ def validate_return_against(doc):
.format(doc.return_against))
def validate_returned_items(doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
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):
valid_items.setdefault(d.item_code, d)
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)
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):
valid_items.setdefault(d.item_code, d)
@ -81,8 +86,20 @@ def validate_returned_items(doc):
elif ref.rate and flt(d.rate) != ref.rate:
frappe.throw(_("Row # {0}: Rate must be same as {1} {2}")
.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
if not items_returned:
@ -134,9 +151,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
},
doctype +" Item": {
"doctype": doctype + " Item",
"fields": {
"field_map": {
"purchase_order": "purchase_order",
"purchase_receipt": "purchase_receipt"
"purchase_receipt": "purchase_receipt",
"serial_no": "serial_no",
"batch_no": "batch_no"
},
"postprocess": update_item
},

View File

@ -218,7 +218,7 @@ class StockController(AccountsController):
tuple(item_codes))
return serialized_items
def get_incoming_rate_for_sales_return(self, item_code, against_document):
incoming_rate = 0.0
if against_document and item_code:
@ -229,12 +229,12 @@ class StockController(AccountsController):
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
return incoming_rate
def update_reserved_qty(self, d):
if d['reserved_qty'] < 0 :
# Reduce reserved qty from reserved warehouse mentioned in so
if not d["reserved_warehouse"]:
frappe.throw(_("Reserved Warehouse is missing in Sales Order"))
frappe.throw(_("Delivery Warehouse is missing in Sales Order"))
args = {
"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
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)
@ -303,9 +303,9 @@ class calculate_taxes_and_totals(object):
for tax in self.doc.get("taxes"):
if tax.category in ["Valuation and Total", "Total"]:
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:
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"])

View File

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

View File

@ -27,7 +27,7 @@ blogs.
"""
app_icon = "icon-th"
app_color = "#e74c3c"
app_version = "5.4.2"
app_version = "5.5.0"
github_link = "https://github.com/frappe/erpnext"
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"""
so_filter = item_filter = ""
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:
so_filter += ' and so.transaction_date <= "' + self.to_date + '"'
so_filter += " and so.transaction_date <= %(to_date)s"
if self.customer:
so_filter += ' and so.customer = "' + self.customer + '"'
so_filter += " and so.customer = %(customer)s"
if self.fg_item:
item_filter += ' and item.name = "' + self.fg_item + '"'
item_filter += " and item.name = %(item)s"
open_so = frappe.db.sql("""
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent = so.name
and so.docstatus = 1 and so.status != "Stopped"
and so.company = %s
and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0) %s
and so.company = %(company)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 (item.is_pro_applicable = 1
or item.is_sub_contracted_item = 1 %s)
and (item.is_pro_applicable = 1 or item.is_sub_contracted_item = 1 {1}))
or exists (select name from `tabPacked Item` pi
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 (item.is_pro_applicable = 1
or item.is_sub_contracted_item = 1) %s)))
""" % ('%s', so_filter, item_filter, item_filter), self.company, as_dict=1)
and (item.is_pro_applicable = 1 or item.is_sub_contracted_item = 1) {2})))
""".format(so_filter, item_filter, item_filter), {
"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)

View File

@ -183,4 +183,8 @@ execute:frappe.delete_doc("DocType", "Party Type")
execute:frappe.delete_doc("Module Def", "Contacts")
erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items # 30-07-2015
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);
// 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);
}
});
@ -365,9 +366,9 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
if (in_list(["Valuation and Total", "Total"], tax.category)) {
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 {
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

@ -36,7 +36,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
if(this.frm.fields_dict["items"]) {
this["items_remove"] = this.calculate_taxes_and_totals;
}
if(this.frm.fields_dict["recurring_print_format"]) {
this.frm.set_query("recurring_print_format", function(doc) {
return{
@ -46,7 +46,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}
});
}
if(this.frm.fields_dict["return_against"]) {
this.frm.set_query("return_against", function(doc) {
var filters = {
@ -56,13 +56,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
};
if (me.frm.fields_dict["customer"] && doc.customer) filters["customer"] = doc.customer;
if (me.frm.fields_dict["supplier"] && doc.supplier) filters["supplier"] = doc.supplier;
return {
filters: filters
}
});
}
},
onload_post_render: function() {
@ -218,7 +218,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
item.serial_no += sr_no[x] + '\n';
refresh_field("serial_no", item.name, item.parentfield);
frappe.model.set_value(item.doctype, item.name, "qty", sr_no.length);
if(!doc.is_return) {
frappe.model.set_value(item.doctype, item.name, "qty", sr_no.length);
}
}
}
},
@ -273,7 +275,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
posting_date: function() {
var me = this;
if (this.frm.doc.posting_date) {
if ((this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.customer) ||
if ((this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.customer) ||
(this.frm.doc.doctype == "Purchase Invoice" && this.frm.doc.supplier)) {
return frappe.call({
method: "erpnext.accounts.party.get_due_date",
@ -282,7 +284,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"party_type": me.frm.doc.doctype == "Sales Invoice" ? "Customer" : "Supplier",
"party": me.frm.doc.doctype == "Sales Invoice" ? me.frm.doc.customer : me.frm.doc.supplier,
"company": me.frm.doc.company
},
},
callback: function(r, rt) {
if(r.message) {
me.frm.set_value("due_date", r.message);
@ -299,7 +301,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
get_company_currency: function() {
return erpnext.get_currency(this.frm.doc.company);
},
contact_person: function() {
erpnext.utils.get_contact_details(this.frm);
},
@ -371,7 +373,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
plc_conversion_rate: function() {
if(this.frm.doc.price_list_currency === this.get_company_currency()) {
this.frm.set_value("plc_conversion_rate", 1.0);
} else if(this.frm.doc.price_list_currency === this.frm.doc.currency
} else if(this.frm.doc.price_list_currency === this.frm.doc.currency
&& this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 &&
cint(this.frm.doc.plc_conversion_rate) != cint(this.frm.doc.conversion_rate)) {
this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate);

View File

@ -83,7 +83,7 @@ $.extend(erpnext, {
return {
filters: {
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_rename": 0,
"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",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "",
"fields": [
{
"allow_on_submit": 0,
"fieldname": "basic_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Parent Item",
"no_copy": 1,
@ -23,30 +43,67 @@
"oldfieldtype": "Data",
"options": "Item",
"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.",
"fieldname": "item_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Items",
"no_copy": 0,
"oldfieldname": "sales_bom_items",
"oldfieldtype": "Table",
"options": "Product Bundle Item",
"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",
"idx": 1,
"in_create": 0,
"in_dialog": 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",
"module": "Selling",
"name": "Product Bundle",
@ -54,14 +111,20 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
@ -69,31 +132,44 @@
{
"amend": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
{
"amend": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
]
],
"read_only": 0,
"read_only_onload": 0
}

View File

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

View File

@ -5,4 +5,20 @@ from __future__ import unicode_literals
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"
}
class WarehouseRequired(frappe.ValidationError): pass
class SalesOrder(SellingController):
def validate_mandatory(self):
# validate transaction date v/s delivery date
@ -40,8 +42,10 @@ class SalesOrder(SellingController):
check_list.append(cstr(d.item_code))
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:
frappe.throw(_("Reserved warehouse required for stock item {0}").format(d.item_code))
(self.has_product_bundle(d.item_code) and self.product_bundle_has_stock_item(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
d.transaction_date = self.transaction_date
@ -53,6 +57,12 @@ class SalesOrder(SellingController):
if len(unique_chk_list) != len(check_list):
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):
for d in self.get('items'):
if d.prevdoc_docname:

View File

@ -6,7 +6,7 @@ from frappe.utils import flt, add_days
import frappe.permissions
import unittest
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):
def tearDown(self):
@ -80,7 +80,7 @@ class TestSalesOrder(unittest.TestCase):
def test_reserved_qty_for_partial_delivery(self):
existing_reserved_qty = get_reserved_qty()
so = make_sales_order()
self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10)
@ -91,7 +91,7 @@ class TestSalesOrder(unittest.TestCase):
so.load_from_db()
so.stop_sales_order()
self.assertEqual(get_reserved_qty(), existing_reserved_qty)
# unstop so
so.load_from_db()
so.unstop_sales_order()
@ -99,7 +99,7 @@ class TestSalesOrder(unittest.TestCase):
dn.cancel()
self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10)
# cancel
so.load_from_db()
so.cancel()
@ -108,9 +108,9 @@ class TestSalesOrder(unittest.TestCase):
def test_reserved_qty_for_over_delivery(self):
# set over-delivery tolerance
frappe.db.set_value('Item', "_Test Item", 'tolerance', 50)
existing_reserved_qty = get_reserved_qty()
so = make_sales_order()
self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10)
@ -124,39 +124,39 @@ class TestSalesOrder(unittest.TestCase):
def test_reserved_qty_for_partial_delivery_with_packing_list(self):
existing_reserved_qty_item1 = get_reserved_qty("_Test Item")
existing_reserved_qty_item2 = get_reserved_qty("_Test Item Home Desktop 100")
so = make_sales_order(item_code="_Test Product Bundle Item")
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 50)
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20)
dn = create_dn_against_so(so.name)
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 25)
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 10)
# stop so
so.load_from_db()
so.stop_sales_order()
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1)
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2)
# unstop so
so.load_from_db()
so.unstop_sales_order()
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 25)
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 10)
dn.cancel()
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 50)
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20)
so.load_from_db()
so.cancel()
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1)
@ -165,25 +165,25 @@ class TestSalesOrder(unittest.TestCase):
def test_reserved_qty_for_over_delivery_with_packing_list(self):
# set over-delivery tolerance
frappe.db.set_value('Item', "_Test Product Bundle Item", 'tolerance', 50)
existing_reserved_qty_item1 = get_reserved_qty("_Test Item")
existing_reserved_qty_item2 = get_reserved_qty("_Test Item Home Desktop 100")
so = make_sales_order(item_code="_Test Product Bundle Item")
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 50)
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20)
dn = create_dn_against_so(so.name, 15)
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1)
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2)
dn.cancel()
self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 50)
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20)
def test_warehouse_user(self):
@ -201,7 +201,7 @@ class TestSalesOrder(unittest.TestCase):
frappe.set_user("test@example.com")
so = make_sales_order(company="_Test Company 1",
so = make_sales_order(company="_Test Company 1",
warehouse="_Test Warehouse 2 - _TC1", do_not_save=True)
so.conversion_rate = 0.02
so.plc_conversion_rate = 0.02
@ -216,14 +216,74 @@ class TestSalesOrder(unittest.TestCase):
def test_block_delivery_note_against_cancelled_sales_order(self):
so = make_sales_order()
dn = make_delivery_note(so.name)
dn.insert()
so.cancel()
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):
so = frappe.new_doc("Sales Order")
args = frappe._dict(args)
@ -234,24 +294,30 @@ def make_sales_order(**args):
so.customer = args.customer or "_Test Customer"
so.delivery_date = add_days(so.transaction_date, 10)
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", {
"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,
"rate": args.rate or 100,
"conversion_factor": 1.0,
})
if not args.do_not_save:
so.insert()
if not args.do_not_submit:
so.submit()
return so
def create_dn_against_so(so, delivered_qty=0):
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
dn = make_delivery_note(so)
dn.get("items")[0].qty = delivered_qty or 5
dn.insert()
@ -261,5 +327,5 @@ def create_dn_against_so(so, delivered_qty=0):
def get_reserved_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"):
return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
"reserved_qty"))
test_dependencies = ["Currency Exchange"]
test_dependencies = ["Currency Exchange"]

View File

@ -1,103 +1,250 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2013-06-25 10:25:16",
"custom": 0,
"description": "Settings for Selling Module",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Other",
"fields": [
{
"allow_on_submit": 0,
"default": "Customer Name",
"fieldname": "cust_master_name",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Customer Naming By",
"no_copy": 0,
"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",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Campaign Naming By",
"no_copy": 0,
"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": "",
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Default Customer Group",
"no_copy": 0,
"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": "",
"fieldname": "territory",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Default Territory",
"no_copy": 0,
"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",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Default Price List",
"no_copy": 0,
"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",
"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",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sales Order Required",
"no_copy": 0,
"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",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Delivery Note Required",
"no_copy": 0,
"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",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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",
"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",
"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",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1,
"modified": "2015-02-05 05:11:46.384538",
"istable": 0,
"modified": "2015-08-03 12:59:51.829458",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
]
],
"read_only": 0,
"read_only_onload": 0
}

View File

@ -30,7 +30,7 @@ class Company(Document):
self.abbr = self.abbr.strip()
if self.get('__islocal') and len(self.abbr) > 5:
frappe.throw(_("Abbreviation cannot have more than 5 characters"))
if not self.abbr.strip():
frappe.throw(_("Abbr can not be blank or space"))
@ -70,7 +70,8 @@ class Company(Document):
frappe.clear_cache()
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)
def create_default_warehouses(self):
@ -183,7 +184,7 @@ class Company(Document):
accounts = frappe.db.sql_list("select name from tabAccount where company=%s", self.name)
cost_centers = frappe.db.sql_list("select name from `tabCost Center` where company=%s", self.name)
warehouses = frappe.db.sql_list("select name from tabWarehouse where company=%s", self.name)
rec = frappe.db.sql("SELECT name from `tabGL Entry` where company = %s", self.name)
if not rec:
# delete Account
@ -202,21 +203,21 @@ class Company(Document):
frappe.db.sql("""delete from `tabWarehouse` where company=%s""", self.name)
frappe.defaults.clear_default("company", value=self.name)
# clear default accounts, warehouses from item
for f in ["default_warehouse", "website_warehouse"]:
frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)"""
frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)"""
% (f, f, ', '.join(['%s']*len(warehouses))), tuple(warehouses))
frappe.db.sql("""delete from `tabItem Reorder` where warehouse in (%s)"""
frappe.db.sql("""delete from `tabItem Reorder` where warehouse in (%s)"""
% ', '.join(['%s']*len(warehouses)), tuple(warehouses))
for f in ["income_account", "expense_account"]:
frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)"""
frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)"""
% (f, f, ', '.join(['%s']*len(accounts))), tuple(accounts))
for f in ["selling_cost_center", "buying_cost_center"]:
frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)"""
frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)"""
% (f, f, ', '.join(['%s']*len(cost_centers))), tuple(cost_centers))
# reset default company
@ -229,7 +230,7 @@ def replace_abbr(company, old, new):
new = new.strip()
if not new:
frappe.throw(_("Abbr can not be blank or space"))
frappe.only_for("System Manager")
frappe.db.set_value("Company", company, "abbr", new)

View File

@ -220,6 +220,7 @@ def set_defaults(args):
stock_settings.valuation_method = "FIFO"
stock_settings.stock_uom = _("Nos")
stock_settings.auto_indent = 1
stock_settings.auto_insert_price_list_rate_if_missing = 1
stock_settings.save()
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 `tabSalary Slip`")
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.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):
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
@ -128,7 +132,7 @@ class Item(WebsiteGenerator):
(self.stock_uom, self.name))
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):
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_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):
def get_item(self, idx):
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() {
var values = d.get_values();
if(!values) return;
values["company"] = cur_frm.doc.company;
frappe.call({
method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items",
args: values,

View File

@ -93,7 +93,7 @@ class StockEntry(StockController):
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,
"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",
"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)
})
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,
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())""",
@ -444,20 +444,28 @@ class StockEntry(StockController):
'actual_qty' : 0,
'incoming_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"]]:
company = frappe.db.get_value(d[0], ret.get(d[1]), "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
# update uom
if args.get("uom") and for_update:
ret.update(self.get_uom_details(args))
if not ret["expense_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 {}
ret.update(stock_and_rate)
return ret
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")
if not conversion_factor:

View File

@ -82,8 +82,7 @@ class StockLedgerEntry(Document):
frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
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):
stock_frozen_upto = frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto') or ''

View File

@ -1,123 +1,343 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2013-06-24 16:37:54",
"custom": 0,
"description": "Settings",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": 0,
"default": "Item Code",
"fieldname": "item_naming_by",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Item Naming By",
"no_copy": 0,
"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": "",
"fieldname": "item_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Default Item Group",
"no_copy": 0,
"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",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Default Stock UOM",
"no_copy": 0,
"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",
"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",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Default Valuation Method",
"no_copy": 0,
"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.",
"fieldname": "tolerance",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"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",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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",
"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",
"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",
"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",
"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",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"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",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Role Allowed to edit frozen stock",
"no_copy": 0,
"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",
"idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1,
"modified": "2015-07-13 05:28:23.839277",
"istable": 0,
"modified": "2015-08-03 13:00:36.082986",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "Stock Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"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)
if not price_list_rate:
if args.price_list and args.rate:
insert_item_price(args)
return {}
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,
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):
return frappe.db.get_value("Item Price",
{"price_list": args.price_list, "item_code": item_code}, "price_list_rate")

View File

@ -300,22 +300,22 @@ class update_entries_after(object):
# select first batch or the batch with same rate
batch = self.stock_queue[index]
if batch[0]:
if qty_to_pop >= batch[0]:
# consume current batch
qty_to_pop = qty_to_pop - batch[0]
self.stock_queue.pop(index)
if not self.stock_queue and qty_to_pop:
# stock finished, qty still remains to be withdrawn
# negative stock, keep in as a negative batch
self.stock_queue.append([-qty_to_pop, outgoing_rate or batch[1]])
break
if qty_to_pop >= batch[0]:
# consume current batch
qty_to_pop = qty_to_pop - batch[0]
self.stock_queue.pop(index)
if not self.stock_queue and qty_to_pop:
# stock finished, qty still remains to be withdrawn
# negative stock, keep in as a negative batch
self.stock_queue.append([-qty_to_pop, outgoing_rate or batch[1]])
break
else:
# qty found in current batch
# consume it and exit
batch[0] = batch[0] - qty_to_pop
qty_to_pop = 0
else:
# qty found in current batch
# consume it and exit
batch[0] = batch[0] - qty_to_pop
qty_to_pop = 0
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue))
stock_qty = sum((flt(batch[0]) for batch in self.stock_queue))

View File

@ -133,19 +133,20 @@ def get_fifo_rate(previous_stock_queue, qty):
qty_to_pop = abs(qty)
while qty_to_pop and previous_stock_queue:
batch = previous_stock_queue[0]
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
available_qty_for_outgoing += flt(batch[0])
outgoing_cost += flt(batch[0]) * flt(batch[1])
qty_to_pop -= batch[0]
previous_stock_queue.pop(0)
else:
# all from current batch
available_qty_for_outgoing += flt(qty_to_pop)
outgoing_cost += flt(qty_to_pop) * flt(batch[1])
batch[0] -= qty_to_pop
qty_to_pop = 0
if batch[0]:
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
available_qty_for_outgoing += flt(batch[0])
outgoing_cost += flt(batch[0]) * flt(batch[1])
qty_to_pop -= batch[0]
previous_stock_queue.pop(0)
else:
# all from current batch
available_qty_for_outgoing += flt(qty_to_pop)
outgoing_cost += flt(qty_to_pop) * flt(batch[1])
batch[0] -= qty_to_pop
qty_to_pop = 0
return outgoing_cost / available_qty_for_outgoing

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
version = "5.4.2"
version = "5.5.0"
with open("requirements.txt", "r") as f:
install_requires = f.readlines()