Merge branch 'version-12-hotfix' into item-attribute

This commit is contained in:
Marica
2020-10-12 17:22:01 +05:30
committed by GitHub
22 changed files with 449 additions and 215 deletions

View File

@@ -31,13 +31,19 @@ apply_on_table = {
}
def get_pricing_rules(args, doc=None):
pricing_rules = []
values = {}
pricing_rules_all = []
values = {}
for apply_on in ['Item Code', 'Item Group', 'Brand']:
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
break
pricing_rules_all.extend(_get_pricing_rules(apply_on, args, values))
# removing duplicate pricing rule
pricing_rules_name = []
pricing_rules = []
for p in pricing_rules_all:
if p['name'] not in pricing_rules_name:
pricing_rules_name.append(p['name'])
pricing_rules.append(p)
rules = []
@@ -323,9 +329,10 @@ def apply_internal_priority(pricing_rules, field_set, args):
filtered_rules = []
for field in field_set:
if args.get(field):
# filter function always returns a filter object even if empty
# list conversion is necessary to check for an empty result
filtered_rules = list(filter(lambda x: x.get(field)==args.get(field), pricing_rules))
for rule in pricing_rules:
if rule.get(field) == args.get(field):
filtered_rules = [rule]
break
if filtered_rules: break
return filtered_rules or pricing_rules

View File

@@ -636,7 +636,8 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount / self.conversion_rate)
}, item=item))
else:
cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account",
asset_category=item.asset_category,company=self.company)
cwip_account_currency = get_account_currency(cwip_account)
gl_entries.append(self.get_gl_dict({

View File

@@ -939,7 +939,8 @@ def make_purchase_invoice(**args):
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"project": args.project,
"rejected_warehouse": args.rejected_warehouse or "",
"rejected_serial_no": args.rejected_serial_no or ""
"rejected_serial_no": args.rejected_serial_no or "",
"asset_location": args.location or ""
})
if args.get_taxes_and_charges:

View File

@@ -470,29 +470,37 @@ class Asset(AccountsController):
def validate_make_gl_entry(self):
purchase_document = self.get_purchase_document()
asset_bought_with_invoice = purchase_document == self.purchase_invoice
fixed_asset_account, cwip_account = self.get_asset_accounts()
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
# check if expense already has been booked in case of cwip was enabled after purchasing asset
expense_booked = False
cwip_booked = False
if asset_bought_with_invoice:
expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
(purchase_document, fixed_asset_account), as_dict=1)
else:
cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
(purchase_document, cwip_account), as_dict=1)
if cwip_enabled and (expense_booked or not cwip_booked):
# if expense has already booked from invoice or cwip is booked from receipt
if not purchase_document:
return False
elif not cwip_enabled and (not expense_booked or cwip_booked):
# if cwip is disabled but expense hasn't been booked yet
return True
elif cwip_enabled:
# default condition
return True
asset_bought_with_invoice = (purchase_document == self.purchase_invoice)
fixed_asset_account = self.get_fixed_asset_account()
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled)
query = """SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s"""
if asset_bought_with_invoice:
# with invoice purchase either expense or cwip has been booked
expense_booked = frappe.db.sql(query, (purchase_document, fixed_asset_account), as_dict=1)
if expense_booked:
# if expense is already booked from invoice then do not make gl entries regardless of cwip enabled/disabled
return False
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
if cwip_booked:
# if cwip is booked from invoice then make gl entries regardless of cwip enabled/disabled
return True
else:
# with receipt purchase either cwip has been booked or no entries have been made
if not cwip_account:
# if cwip account isn't available do not make gl entries
return False
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
# if cwip is not booked from receipt then do not make gl entries
# if cwip is booked from receipt then make gl entries
return cwip_booked
def get_purchase_document(self):
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
@@ -500,20 +508,25 @@ class Asset(AccountsController):
return purchase_document
def get_asset_accounts(self):
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
asset_category = self.asset_category, company = self.company)
def get_fixed_asset_account(self):
return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
def get_cwip_account(self, cwip_enabled=False):
cwip_account = None
try:
cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company)
except:
# if no cwip account found in category or company and "cwip is enabled" then raise else silently pass
if cwip_enabled:
raise
cwip_account = get_asset_account("capital_work_in_progress_account",
self.name, self.asset_category, self.company)
return fixed_asset_account, cwip_account
return cwip_account
def make_gl_entries(self):
gl_entries = []
purchase_document = self.get_purchase_document()
fixed_asset_account, cwip_account = self.get_asset_accounts()
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):

View File

@@ -9,6 +9,7 @@ from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, ad
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset
from erpnext.assets.doctype.asset.asset import make_sales_invoice
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice
class TestAsset(unittest.TestCase):
@@ -561,81 +562,6 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
def test_gle_with_cwip_toggling(self):
# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=5000, do_not_submit=True, location="Test Location")
pr.set('taxes', [{
'category': 'Total',
'add_deduct_tax': 'Add',
'charge_type': 'On Net Total',
'account_head': '_Test Account Service Tax - _TC',
'description': '_Test Account Service Tax',
'cost_center': 'Main - _TC',
'rate': 5.0
}, {
'category': 'Valuation and Total',
'add_deduct_tax': 'Add',
'charge_type': 'On Net Total',
'account_head': '_Test Account Shipping Charges - _TC',
'description': '_Test Account Shipping Charges',
'cost_center': 'Main - _TC',
'rate': 5.0
}])
pr.submit()
expected_gle = (
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
("CWIP Account - _TC", 5250.0, 0.0)
)
pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no = %s
order by account""", pr.name)
self.assertEqual(pr_gle, expected_gle)
pi = make_invoice(pr.name)
pi.submit()
expected_gle = (
("_Test Account Service Tax - _TC", 250.0, 0.0),
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
("Creditors - _TC", 0.0, 5500.0),
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
)
pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no = %s
order by account""", pi.name)
self.assertEqual(pi_gle, expected_gle)
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
month_end_date = get_last_day(nowdate())
asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
asset_doc.append("finance_books", {
"expected_value_after_useful_life": 200,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": month_end_date
})
# disable cwip and try submitting
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
asset_doc.submit()
# asset should have gl entries even if cwip is disabled
expected_gle = (
("_Test Fixed Asset - _TC", 5250.0, 0.0),
("CWIP Account - _TC", 0.0, 5250.0)
)
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Asset' and voucher_no = %s
order by account""", asset_doc.name)
self.assertEqual(gle, expected_gle)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
def test_expense_head(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=2, rate=200000.0, location="Test Location")
@@ -643,6 +569,74 @@ class TestAsset(unittest.TestCase):
doc = make_invoice(pr.name)
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
def test_asset_cwip_toggling_cases(self):
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
cwip_acc = "CWIP Account - _TC"
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", "")
# case 0 -- PI with cwip disable, Asset with cwip disabled, No cwip account set
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertFalse(gle)
# case 1 -- PR with cwip disabled, Asset with cwip enabled
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertFalse(gle)
# case 2 -- PR with cwip enabled, Asset with cwip disabled
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertTrue(gle)
# case 3 -- PI with cwip disabled, Asset with cwip enabled
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertFalse(gle)
# case 4 -- PI with cwip enabled, Asset with cwip disabled
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertTrue(gle)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):

View File

@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint
from frappe.utils import cint, get_link_to_form
from frappe.model.document import Document
class AssetCategory(Document):
@@ -13,6 +13,7 @@ class AssetCategory(Document):
self.validate_finance_books()
self.validate_account_types()
self.validate_account_currency()
self.valide_cwip_account()
def validate_finance_books(self):
for d in self.finance_books:
@@ -58,6 +59,21 @@ class AssetCategory(Document):
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
title=_("Invalid Account"))
def valide_cwip_account(self):
if self.enable_cwip_accounting:
missing_cwip_accounts_for_company = []
for d in self.accounts:
if (not d.capital_work_in_progress_account and
not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")):
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
if missing_cwip_accounts_for_company:
msg = _("""To enable Capital Work in Progress Accounting, """)
msg += _("""you must select Capital Work in Progress Account in accounts table""")
msg += "<br><br>"
msg += _("You can also set default CWIP account in Company {}").format(", ".join(missing_cwip_accounts_for_company))
frappe.throw(msg, title=_("Missing Account"))
@frappe.whitelist()

View File

@@ -26,4 +26,22 @@ class TestAssetCategory(unittest.TestCase):
asset_category.insert()
except frappe.DuplicateEntryError:
pass
def test_cwip_accounting(self):
company_cwip_acc = frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account")
frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "")
asset_category = frappe.new_doc("Asset Category")
asset_category.asset_category_name = "Computers"
asset_category.enable_cwip_accounting = 1
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
asset_category.append("accounts", {
"company_name": "_Test Company",
"fixed_asset_account": "_Test Fixed Asset - _TC",
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
"depreciation_expense_account": "_Test Depreciations - _TC"
})
self.assertRaises(frappe.ValidationError, asset_category.insert)

View File

@@ -143,7 +143,7 @@ def get_conditions(filters):
conditions = ""
if filters.get("company"):
conditions += " AND par.company=%s" % frappe.db.escape(filters.get('company'))
conditions += " AND parent.company=%s" % frappe.db.escape(filters.get('company'))
if filters.get("cost_center") or filters.get("project"):
conditions += """
@@ -151,10 +151,10 @@ def get_conditions(filters):
""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
if filters.get("from_date"):
conditions += " AND par.transaction_date>='%s'" % filters.get('from_date')
conditions += " AND parent.transaction_date>='%s'" % filters.get('from_date')
if filters.get("to_date"):
conditions += " AND par.transaction_date<='%s'" % filters.get('to_date')
conditions += " AND parent.transaction_date<='%s'" % filters.get('to_date')
return conditions
def get_data(filters):
@@ -198,21 +198,23 @@ def get_mapped_mr_details(conditions):
mr_records = {}
mr_details = frappe.db.sql("""
SELECT
par.transaction_date,
par.per_ordered,
par.owner,
parent.transaction_date,
parent.per_ordered,
parent.owner,
child.name,
child.parent,
child.amount,
child.qty,
child.item_code,
child.uom,
par.status
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
parent.status,
child.project,
child.cost_center
FROM `tabMaterial Request` parent, `tabMaterial Request Item` child
WHERE
par.per_ordered>=0
AND par.name=child.parent
AND par.docstatus=1
parent.per_ordered>=0
AND parent.name=child.parent
AND parent.docstatus=1
{conditions}
""".format(conditions=conditions), as_dict=1) #nosec
@@ -232,7 +234,9 @@ def get_mapped_mr_details(conditions):
status=record.status,
actual_cost=0,
purchase_order_amt=0,
purchase_order_amt_in_company_currency=0
purchase_order_amt_in_company_currency=0,
project = record.project,
cost_center = record.cost_center
)
procurement_record_against_mr.append(procurement_record_details)
return mr_records, procurement_record_against_mr
@@ -280,16 +284,16 @@ def get_po_entries(conditions):
child.amount,
child.base_amount,
child.schedule_date,
par.transaction_date,
par.supplier,
par.status,
par.owner
FROM `tabPurchase Order` par, `tabPurchase Order Item` child
parent.transaction_date,
parent.supplier,
parent.status,
parent.owner
FROM `tabPurchase Order` parent, `tabPurchase Order Item` child
WHERE
par.docstatus = 1
AND par.name = child.parent
AND par.status not in ("Closed","Completed","Cancelled")
parent.docstatus = 1
AND parent.name = child.parent
AND parent.status not in ("Closed","Completed","Cancelled")
{conditions}
GROUP BY
par.name, child.item_code
parent.name, child.item_code
""".format(conditions=conditions), as_dict=1) #nosec

View File

@@ -629,22 +629,29 @@ class calculate_taxes_and_totals(object):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def update_paid_amount_for_return(self, total_amount_to_pay):
default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment',
{'parent': self.doc.pos_profile, 'default': 1},
['mode_of_payment', 'type', 'account'], as_dict=1)
existing_amount = 0
self.doc.payments = []
for payment in self.doc.payments:
existing_amount += payment.amount
if default_mode_of_payment:
self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment,
'type': default_mode_of_payment.type,
'account': default_mode_of_payment.account,
'amount': total_amount_to_pay
})
else:
self.doc.is_pos = 0
self.doc.pos_profile = ''
# do not override user entered amount if equal to total_amount_to_pay
if existing_amount != total_amount_to_pay:
default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment',
{'parent': self.doc.pos_profile, 'default': 1},
['mode_of_payment', 'type', 'account'], as_dict=1)
self.doc.payments = []
if default_mode_of_payment:
self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment,
'type': default_mode_of_payment.type,
'account': default_mode_of_payment.account,
'amount': total_amount_to_pay
})
else:
self.doc.is_pos = 0
self.doc.pos_profile = ''
self.calculate_paid_amount()

View File

@@ -251,6 +251,10 @@ def make_bom(**args):
'rate': item_doc.valuation_rate or args.rate,
})
bom.insert(ignore_permissions=True)
bom.submit()
if not args.do_not_save:
bom.insert(ignore_permissions=True)
if not args.do_not_submit:
bom.submit()
return bom

View File

@@ -371,6 +371,49 @@ class TestWorkOrder(unittest.TestCase):
ste1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1))
self.assertEqual(len(ste1.items), 3)
def test_operation_time_with_batch_size(self):
fg_item = "Test Batch Size Item For BOM"
rm1 = "Test Batch Size Item RM 1 For BOM"
for item in ["Test Batch Size Item For BOM", "Test Batch Size Item RM 1 For BOM"]:
make_item(item, {
"include_item_in_manufacturing": 1,
"is_stock_item": 1
})
bom_name = frappe.db.get_value("BOM",
{"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
if not bom_name:
bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
bom.with_operations = 1
bom.append("operations", {
"operation": "_Test Operation 1",
"workstation": "_Test Workstation 1",
"description": "Test Data",
"operating_cost": 100,
"time_in_mins": 40,
"batch_size": 5
})
bom.save()
bom.submit()
bom_name = bom.name
work_order = make_wo_order_test_record(item=fg_item,
planned_start_date=now(), qty=1, do_not_save=True)
work_order.set_work_order_operations()
work_order.save()
self.assertEqual(work_order.operations[0].time_in_mins, 8.0)
work_order1 = make_wo_order_test_record(item=fg_item,
planned_start_date=now(), qty=5, do_not_save=True)
work_order1.set_work_order_operations()
work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
def get_scrap_item_details(bom_no):
scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`

View File

@@ -611,7 +611,7 @@ erpnext.work_order = {
description: __('Max: {0}', [max]),
default: max
}, data => {
max += (max * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
max += (frm.doc.qty * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
if (data.qty > max) {
frappe.msgprint(__('Quantity must not be more than {0}', [max]));

View File

@@ -364,7 +364,7 @@ class WorkOrder(Document):
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
for d in self.get("operations"):
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * math.ceil(flt(self.qty) / flt(d.batch_size))
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * (flt(self.qty) / flt(d.batch_size))
self.calculate_operating_cost()

View File

@@ -594,7 +594,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
$.each(actual_taxes_dict, function(key, value) {
if (value) total_actual_tax += value;
});
return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total"));
}
},
@@ -672,25 +672,33 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
);
}
frappe.db.get_value('Sales Invoice Payment', {'parent': this.frm.doc.pos_profile, 'default': 1},
['mode_of_payment', 'account', 'type'], (value) => {
if (this.frm.is_dirty()) {
frappe.model.clear_table(this.frm.doc, 'payments');
if (value) {
let row = frappe.model.add_child(this.frm.doc, 'Sales Invoice Payment', 'payments');
row.mode_of_payment = value.mode_of_payment;
row.type = value.type;
row.account = value.account;
row.default = 1;
row.amount = total_amount_to_pay;
} else {
this.frm.set_value('is_pos', 1);
}
this.frm.refresh_fields();
}
}, 'Sales Invoice');
let existing_amount = 0
$.each(this.frm.doc.payments || [], function(i, row) {
existing_amount += row.amount;
})
this.calculate_paid_amount();
if (existing_amount != total_amount_to_pay) {
frappe.db.get_value('Sales Invoice Payment', {'parent': this.frm.doc.pos_profile, 'default': 1},
['mode_of_payment', 'account', 'type'], (value) => {
if (this.frm.is_dirty()) {
frappe.model.clear_table(this.frm.doc, 'payments');
if (value) {
let row = frappe.model.add_child(this.frm.doc, 'Sales Invoice Payment', 'payments');
row.mode_of_payment = value.mode_of_payment;
row.type = value.type;
row.account = value.account;
row.default = 1;
row.amount = total_amount_to_pay;
} else {
this.frm.set_value('is_pos', 1);
}
this.frm.refresh_fields();
this.calculate_paid_amount();
}
}, 'Sales Invoice');
} else {
this.calculate_paid_amount();
}
},
set_default_payment: function(total_amount_to_pay, update_paid_amount) {

View File

@@ -50,16 +50,18 @@ class ItemPrice(Document):
def check_duplicates(self):
conditions = "where item_code=%(item_code)s and price_list=%(price_list)s and name != %(name)s"
condition_data_dict = dict(item_code=self.item_code, price_list=self.price_list, name=self.name)
for field in ['uom', 'valid_from',
'valid_upto', 'packing_unit', 'customer', 'supplier']:
if self.get(field):
conditions += " and {0} = %({1})s".format(field, field)
condition_data_dict[field] = self.get(field)
price_list_rate = frappe.db.sql("""
SELECT price_list_rate
FROM `tabItem Price`
{conditions} """.format(conditions=conditions), self.as_dict())
{conditions} """.format(conditions=conditions), condition_data_dict)
if price_list_rate :
frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty and Dates."), ItemPriceDuplicateItem)

View File

@@ -172,8 +172,9 @@ class StockReconciliation(StockController):
row.serial_no = ''
# item managed batch-wise not allowed
if item.has_batch_no and not row.batch_no and not item.create_new_batch:
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
if item.has_batch_no and not row.batch_no and not frappe.flags.in_test:
if not item.create_new_batch or self.purpose != 'Opening Stock':
raise frappe.ValidationError(_("Batch no is required for the batched item {0}").format(item_code))
# docstatus should be < 2
validate_cancelled_item(item_code, item.docstatus, verbose=0)
@@ -191,10 +192,11 @@ class StockReconciliation(StockController):
serialized_items = False
for row in self.items:
item = frappe.get_cached_doc("Item", row.item_code)
if not (item.has_serial_no or item.has_batch_no):
if row.serial_no or row.batch_no:
if not (item.has_serial_no):
if row.serial_no:
frappe.throw(_("Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.") \
.format(row.idx, frappe.bold(row.item_code)))
previous_sle = get_previous_sle({
"item_code": row.item_code,
"warehouse": row.warehouse,
@@ -217,7 +219,12 @@ class StockReconciliation(StockController):
or (not previous_sle and not row.qty)):
continue
sl_entries.append(self.get_sle_for_items(row))
sle_data = self.get_sle_for_items(row)
if row.batch_no:
sle_data.actual_qty = row.quantity_difference
sl_entries.append(sle_data)
else:
serialized_items = True
@@ -244,7 +251,7 @@ class StockReconciliation(StockController):
serial_nos = get_serial_nos(row.serial_no) or []
# To issue existing serial nos
if row.current_qty and (row.current_serial_no or row.batch_no):
if row.current_qty and (row.current_serial_no):
args = self.get_sle_for_items(row)
args.update({
'actual_qty': -1 * row.current_qty,

View File

@@ -361,6 +361,37 @@ class TestStockReconciliation(unittest.TestCase):
doc.cancel()
frappe.delete_doc(doc.doctype, doc.name)
def test_allow_negative_for_batch(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
item_code = "Stock-Reco-batch-Item-5"
warehouse = "_Test Warehouse for Stock Reco5 - _TC"
create_warehouse("_Test Warehouse for Stock Reco5", {"is_group": 0,
"parent_warehouse": "_Test Warehouse Group - _TC", "company": "_Test Company"})
batch_item_doc = create_item(item_code, is_stock_item=1)
if not batch_item_doc.has_batch_no:
frappe.db.set_value("Item", item_code, {
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "Test-C.####"
})
ste1=make_stock_entry(posting_date="2020-10-07", posting_time="02:00", item_code=item_code,
target=warehouse, qty=2, basic_rate=100)
batch_no = ste1.items[0].batch_no
ste2=make_stock_entry(posting_date="2020-10-09", posting_time="02:00", item_code=item_code,
source=warehouse, qty=2, basic_rate=100, batch_no=batch_no)
sr = create_stock_reconciliation(item_code=item_code,
warehouse = warehouse, batch_no=batch_no, rate=200)
for doc in [sr, ste2, ste1]:
doc.cancel()
frappe.delete_doc(doc.doctype, doc.name)
def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

View File

@@ -3,6 +3,14 @@
frappe.query_reports["Batch-Wise Balance History"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
"fieldname":"from_date",
"label": __("From Date"),
@@ -18,6 +26,47 @@ frappe.query_reports["Batch-Wise Balance History"] = {
"width": "80",
"default": frappe.datetime.get_today(),
"reqd": 1
}
},
{
"fieldname":"item_code",
"label": __("Item Code"),
"fieldtype": "Link",
"options": "Item",
"get_query": function() {
return {
filters: {
"has_batch_no": 1
}
}
}
},
{
"fieldname":"warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse",
"get_query": function() {
let company = frappe.query_report.get_filter_value('company');
return {
filters: {
"company": company
}
}
}
},
{
"fieldname":"batch_no",
"label": __("Batch No"),
"fieldtype": "Link",
"options": "Batch",
"get_query": function() {
let item_code = frappe.query_report.get_filter_value('item_code');
return {
filters: {
"item": item_code
}
}
}
},
]
}

View File

@@ -53,6 +53,10 @@ def get_conditions(filters):
else:
frappe.throw(_("'To Date' is required"))
for field in ["item_code", "warehouse", "batch_no", "company"]:
if filters.get(field):
conditions += " and {0} = {1}".format(field, frappe.db.escape(filters.get(field)))
return conditions
#get all details

View File

@@ -77,38 +77,33 @@ def get_price_list():
return item_rate_map
def get_last_purchase_rate():
item_last_purchase_rate_map = {}
query = """select * from (select
result.item_code,
result.base_rate
from (
(select
po_item.item_code,
po_item.item_name,
po.transaction_date as posting_date,
po_item.base_price_list_rate,
po_item.discount_percentage,
po_item.base_rate
from `tabPurchase Order` po, `tabPurchase Order Item` po_item
where po.name = po_item.parent and po.docstatus = 1)
union
(select
pr_item.item_code,
pr_item.item_name,
pr.posting_date,
pr_item.base_price_list_rate,
pr_item.discount_percentage,
pr_item.base_rate
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item
where pr.name = pr_item.parent and pr.docstatus = 1)
) result
order by result.item_code asc, result.posting_date desc) result_wrapper
group by item_code"""
query = """select * from (
(select
po_item.item_code,
po.transaction_date as posting_date,
po_item.base_rate
from `tabPurchase Order` po, `tabPurchase Order Item` po_item
where po.name = po_item.parent and po.docstatus = 1)
union
(select
pr_item.item_code,
pr.posting_date,
pr_item.base_rate
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item
where pr.name = pr_item.parent and pr.docstatus = 1)
union
(select
pi_item.item_code,
pi.posting_date,
pi_item.base_rate
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item
where pi.name = pi_item.parent and pi.docstatus = 1 and pi.update_stock = 1)
) result order by result.item_code asc, result.posting_date asc"""
for d in frappe.db.sql(query, as_dict=1):
item_last_purchase_rate_map.setdefault(d.item_code, d.base_rate)
item_last_purchase_rate_map[d.item_code] = d.base_rate
return item_last_purchase_rate_map

View File

@@ -6,6 +6,7 @@ import frappe
from frappe import _
from frappe.utils import cint, flt
from erpnext.stock.utils import update_included_uom_in_report
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
def execute(filters=None):
include_uom = filters.get("include_uom")
@@ -23,6 +24,7 @@ def execute(filters=None):
actual_qty = stock_value = 0
available_serial_nos = {}
for sle in sl_entries:
item_detail = item_details[sle.item_code]
@@ -41,6 +43,9 @@ def execute(filters=None):
"stock_value": stock_value
})
if sle.serial_no:
update_available_serial_nos(available_serial_nos, sle)
data.append(sle)
if include_uom:
@@ -49,6 +54,27 @@ def execute(filters=None):
update_included_uom_in_report(columns, data, include_uom, conversion_factors)
return columns, data
def update_available_serial_nos(available_serial_nos, sle):
serial_nos = get_serial_nos(sle.serial_no)
key = (sle.item_code, sle.warehouse)
if key not in available_serial_nos:
available_serial_nos.setdefault(key, [])
existing_serial_no = available_serial_nos[key]
for sn in serial_nos:
if sle.actual_qty > 0:
if sn in existing_serial_no:
existing_serial_no.remove(sn)
else:
existing_serial_no.append(sn)
else:
if sn in existing_serial_no:
existing_serial_no.remove(sn)
else:
existing_serial_no.append(sn)
sle.balance_serial_no = '\n'.join(existing_serial_no)
def get_columns():
columns = [
{"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 95},
@@ -70,7 +96,8 @@ def get_columns():
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
{"label": _("Serial #"), "fieldname": "serial_no", "width": 100},
{"label": _("Serial No"), "fieldname": "serial_no", "width": 100},
{"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
{"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 110}
]

View File

@@ -162,10 +162,13 @@ class update_entries_after(object):
self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
else:
if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no:
# assert
if sle.voucher_type=="Stock Reconciliation":
if sle.batch_no:
self.qty_after_transaction += flt(sle.actual_qty)
else:
self.qty_after_transaction = sle.qty_after_transaction
self.valuation_rate = sle.valuation_rate
self.qty_after_transaction = sle.qty_after_transaction
self.stock_queue = [[self.qty_after_transaction, self.valuation_rate]]
self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
else: