Merge pull request #3823 from nabinhait/stock-entry-tax

Stock entry tax
This commit is contained in:
Rushabh Mehta 2015-08-10 14:31:20 +05:30
commit 6f593130d0
23 changed files with 1465 additions and 308 deletions

View File

@ -746,7 +746,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_return_sales_invoice(self):
set_perpetual_inventory()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100)
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
actual_qty_0 = get_qty_after_transaction()

View File

@ -11,8 +11,7 @@ from erpnext.accounts.utils import validate_expense_against_budget
class StockAccountInvalidTransaction(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True,
update_outstanding='Yes'):
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
if gl_map:
if not cancel:
gl_map = process_gl_map(gl_map, merge_entries)

View File

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

View File

@ -0,0 +1,2 @@
- Additional Costs in Stock Entry **[Sponsored by PT. Ridho Sribumi Sejahtera]**
- Now additional costs like shipping charges, operating costs etc can be added in Stock Entry in item valuation

View File

@ -10,6 +10,9 @@ from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from dateutil.relativedelta import relativedelta
from erpnext.stock.doctype.item.item import validate_end_of_life
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
from erpnext.projects.doctype.time_log.time_log import OverlapError
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass
@ -17,9 +20,6 @@ class OperationTooLongError(frappe.ValidationError): pass
class ProductionNotApplicableError(frappe.ValidationError): pass
class ItemHasVariantError(frappe.ValidationError): pass
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
from erpnext.projects.doctype.time_log.time_log import OverlapError
form_grid_templates = {
"operations": "templates/form_grid/production_order_grid.html"
}
@ -356,7 +356,6 @@ def make_stock_entry(production_order_id, purpose, qty=None):
stock_entry.company = production_order.company
stock_entry.from_bom = 1
stock_entry.bom_no = production_order.bom_no
stock_entry.additional_operating_cost = production_order.additional_operating_cost
stock_entry.use_multi_level_bom = production_order.use_multi_level_bom
stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty))
@ -365,6 +364,8 @@ def make_stock_entry(production_order_id, purpose, qty=None):
else:
stock_entry.from_warehouse = production_order.wip_warehouse
stock_entry.to_warehouse = production_order.fg_warehouse
additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty)
stock_entry.set("additional_costs", additional_costs)
stock_entry.get_items()
return stock_entry.as_dict()

View File

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

View File

@ -189,3 +189,4 @@ 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")
erpnext.patches.v5_4.fix_missing_item_images
erpnext.patches.v5_4.stock_entry_additional_costs

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -29,8 +29,7 @@ class StockEntry(StockController):
def onload(self):
if self.docstatus==1:
for item in self.get("items"):
item.update(get_available_qty(item.item_code,
item.s_warehouse))
item.update(get_available_qty(item.item_code, item.s_warehouse))
def validate(self):
self.pro_doc = None
@ -46,14 +45,13 @@ class StockEntry(StockController):
self.validate_uom_is_integer("stock_uom", "transfer_qty")
self.validate_warehouse()
self.validate_production_order()
self.get_stock_and_rate()
self.validate_bom()
self.validate_finished_goods()
self.validate_with_material_request()
self.validate_valuation_rate()
self.set_total_incoming_outgoing_value()
self.set_total_amount()
self.validate_batch()
self.set_actual_qty()
self.calculate_rate_and_amount()
def on_submit(self):
self.update_stock_ledger()
@ -213,6 +211,88 @@ class StockEntry(StockController):
if fg_qty_already_entered >= qty:
frappe.throw(_("Stock Entries already created for Production Order ")
+ self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError)
def set_actual_qty(self):
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
for d in self.get('items'):
previous_sle = get_previous_sle({
"item_code": d.item_code,
"warehouse": d.s_warehouse or d.t_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time
})
# get actual stock at source warehouse
d.actual_qty = previous_sle.get("qty_after_transaction") or 0
# validate qty during submit
if d.docstatus==1 and d.s_warehouse and not allow_negative_stock and d.actual_qty < d.transfer_qty:
frappe.throw(_("""Row {0}: Qty not avalable in warehouse {1} on {2} {3}.
Available Qty: {4}, Transfer Qty: {5}""").format(d.idx, d.s_warehouse,
self.posting_date, self.posting_time, d.actual_qty, d.transfer_qty), NegativeStockError)
def calculate_rate_and_amount(self, force=False):
self.set_basic_rate(force)
self.distribute_additional_costs()
self.update_valuation_rate()
self.validate_valuation_rate()
self.set_total_incoming_outgoing_value()
self.set_total_amount()
def set_basic_rate(self, force=False):
"""get stock and incoming rate on posting date"""
raw_material_cost = 0.0
for d in self.get('items'):
args = frappe._dict({
"item_code": d.item_code,
"warehouse": d.s_warehouse or d.t_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": d.s_warehouse and -1*flt(d.transfer_qty) or flt(d.transfer_qty),
"serial_no": d.serial_no,
})
# get basic rate
if not d.bom_no:
if not flt(d.basic_rate) or d.s_warehouse or force:
basic_rate = flt(get_incoming_rate(args), self.precision("basic_rate", d))
if basic_rate > 0:
d.basic_rate = basic_rate
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
if not d.t_warehouse:
raw_material_cost += flt(d.basic_amount)
self.set_basic_rate_for_finished_goods(raw_material_cost)
def set_basic_rate_for_finished_goods(self, raw_material_cost):
if self.purpose in ["Manufacture", "Repack"]:
number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse])
for d in self.get("items"):
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1):
d.basic_rate = flt(raw_material_cost / flt(d.transfer_qty), d.precision("basic_rate"))
d.basic_amount = flt(flt(d.basic_rate) * flt(d.transfer_qty), d.precision("basic_amount"))
def distribute_additional_costs(self):
if self.purpose == "Material Issue":
self.additional_costs = []
self.total_additional_costs = sum([flt(t.amount) for t in self.get("additional_costs")])
total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
for d in self.get("items"):
if d.t_warehouse and total_basic_amount:
d.additional_cost = (flt(d.basic_amount) / total_basic_amount) * self.total_additional_costs
else:
d.additional_cost = 0
def update_valuation_rate(self):
for d in self.get("items"):
d.amount = flt(d.basic_amount + flt(d.additional_cost), d.precision("amount"))
d.valuation_rate = flt(flt(d.basic_rate) + flt(d.additional_cost) / flt(d.transfer_qty),
d.precision("valuation_rate"))
def validate_valuation_rate(self):
if self.purpose in ["Manufacture", "Repack"]:
@ -224,100 +304,22 @@ class StockEntry(StockController):
valuation_at_target += flt(d.amount)
if valuation_at_target + 0.001 < valuation_at_source:
frappe.throw(_("Total valuation ({0}) for manufactured or repacked item(s) can not be less than total valuation of raw materials ({1})").format(valuation_at_target,
valuation_at_source))
frappe.throw(_("Total valuation ({0}) for manufactured or repacked item(s) can not be less than total valuation of raw materials ({1})")
.format(valuation_at_target, valuation_at_source))
def set_total_incoming_outgoing_value(self):
self.total_incoming_value = self.total_outgoing_value = 0.0
for d in self.get("items"):
if d.s_warehouse:
self.total_incoming_value += flt(d.amount)
if d.t_warehouse:
self.total_incoming_value += flt(d.amount)
if d.s_warehouse:
self.total_outgoing_value += flt(d.amount)
self.value_difference = self.total_outgoing_value - self.total_incoming_value
self.value_difference = self.total_incoming_value - self.total_outgoing_value
def set_total_amount(self):
self.total_amount = sum([flt(item.amount) for item in self.get("items")])
def get_stock_and_rate(self, force=False):
"""get stock and incoming rate on posting date"""
raw_material_cost = 0.0
if not self.posting_date or not self.posting_time:
frappe.throw(_("Posting date and posting time is mandatory"))
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
for d in self.get('items'):
d.transfer_qty = flt(d.transfer_qty)
args = frappe._dict({
"item_code": d.item_code,
"warehouse": d.s_warehouse or d.t_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
"serial_no": d.serial_no,
})
# get actual stock at source warehouse
d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0
# validate qty during submit
if d.docstatus==1 and d.s_warehouse and not allow_negative_stock and d.actual_qty < d.transfer_qty:
frappe.throw(_("""Row {0}: Qty not avalable in warehouse {1} on {2} {3}.
Available Qty: {4}, Transfer Qty: {5}""").format(d.idx, d.s_warehouse,
self.posting_date, self.posting_time, d.actual_qty, d.transfer_qty), NegativeStockError)
# get incoming rate
if not d.bom_no:
if not flt(d.incoming_rate) or d.s_warehouse or force:
incoming_rate = flt(get_incoming_rate(args), self.precision("incoming_rate", d))
if incoming_rate > 0:
d.incoming_rate = incoming_rate
d.amount = flt(flt(d.transfer_qty) * flt(d.incoming_rate), d.precision("amount"))
if not d.t_warehouse:
raw_material_cost += flt(d.amount)
self.add_operation_cost(raw_material_cost, force)
def add_operation_cost(self, raw_material_cost, force):
"""Adds operating cost if Production Order is set"""
# set incoming rate for fg item
if self.purpose in ["Manufacture", "Repack"]:
number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse])
for d in self.get("items"):
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1):
operation_cost_per_unit = self.get_operation_cost_per_unit(d.bom_no, d.qty)
d.incoming_rate = operation_cost_per_unit + (raw_material_cost / flt(d.transfer_qty))
d.amount = flt(flt(d.transfer_qty) * flt(d.incoming_rate), self.precision("transfer_qty", d))
break
def get_operation_cost_per_unit(self, bom_no, qty):
"""Returns operating cost from Production Order for given `bom_no`"""
operation_cost_per_unit = 0
if self.production_order:
if not getattr(self, "pro_doc", None):
self.pro_doc = frappe.get_doc("Production Order", self.production_order)
for d in self.pro_doc.get("operations"):
if flt(d.completed_qty):
operation_cost_per_unit += flt(d.actual_operating_cost) / flt(d.completed_qty)
else:
operation_cost_per_unit += flt(d.planned_operating_cost) / flt(self.pro_doc.qty)
# set operating cost from BOM if specified.
if not operation_cost_per_unit and bom_no:
bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1)
operation_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)
return operation_cost_per_unit + (flt(self.additional_operating_cost) / flt(qty))
def validate_purchase_order(self):
"""Throw exception if more raw material is transferred against Purchase Order than in
the raw materials supplied table"""
@ -366,7 +368,7 @@ class StockEntry(StockController):
def update_stock_ledger(self):
sl_entries = []
for d in self.get('items'):
for d in self.get('items'):
if cstr(d.s_warehouse) and self.docstatus == 1:
sl_entries.append(self.get_sl_entries(d, {
"warehouse": cstr(d.s_warehouse),
@ -378,7 +380,7 @@ class StockEntry(StockController):
sl_entries.append(self.get_sl_entries(d, {
"warehouse": cstr(d.t_warehouse),
"actual_qty": flt(d.transfer_qty),
"incoming_rate": flt(d.incoming_rate)
"incoming_rate": flt(d.valuation_rate)
}))
# On cancellation, make stock ledger entry for
@ -392,6 +394,32 @@ class StockEntry(StockController):
}))
self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No')
def get_gl_entries(self, warehouse_account):
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
for d in self.get("items"):
additional_cost = flt(d.additional_cost, d.precision("additional_cost"))
if additional_cost:
gl_entries.append(self.get_gl_dict({
"account": expenses_included_in_valuation,
"against": d.expense_account,
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": additional_cost
}))
gl_entries.append(self.get_gl_dict({
"account": d.expense_account,
"against": expenses_included_in_valuation,
"cost_center": d.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": -1 * additional_cost # put it as negative credit instead of debit purposefully
}))
return gl_entries
def update_production_order(self):
def _validate_production_order(pro_doc):
@ -442,7 +470,7 @@ class StockEntry(StockController):
'conversion_factor' : 1,
'batch_no' : '',
'actual_qty' : 0,
'incoming_rate' : 0
'basic_rate' : 0
}
for d in [["Account", "expense_account", "default_expense_account"],
["Cost Center", "cost_center", "cost_center"]]:
@ -490,13 +518,16 @@ class StockEntry(StockController):
ret = {
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
"incoming_rate" : get_incoming_rate(args)
"basic_rate" : get_incoming_rate(args)
}
return ret
def get_items(self):
self.set('items', [])
self.validate_production_order()
if not self.posting_date or not self.posting_time:
frappe.throw(_("Posting date and posting time is mandatory"))
if not getattr(self, "pro_doc", None):
self.pro_doc = None
@ -538,7 +569,8 @@ class StockEntry(StockController):
if self.purpose in ("Manufacture", "Repack"):
self.load_items_from_bom()
self.get_stock_and_rate()
self.set_actual_qty()
self.calculate_rate_and_amount()
def load_items_from_bom(self):
if self.production_order:
@ -667,12 +699,56 @@ class StockEntry(StockController):
if getdate(self.posting_date) > getdate(expiry_date):
frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
@frappe.whitelist()
def get_production_order_details(production_order):
res = frappe.db.sql("""select bom_no, use_multi_level_bom, wip_warehouse,
ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty,
(ifnull(additional_operating_cost, 0) / qty)*(ifnull(qty, 0) - ifnull(produced_qty, 0)) as additional_operating_cost
from `tabProduction Order` where name = %s""", production_order, as_dict=1)
return res and res[0] or {}
production_order = frappe.get_doc("Production Order", production_order)
pending_qty_to_produce = flt(production_order.qty) - flt(production_order.produced_qty)
return {
"from_bom": 1,
"bom_no": production_order.bom_no,
"use_multi_level_bom": production_order.use_multi_level_bom,
"wip_warehouse": production_order.wip_warehouse,
"fg_warehouse": production_order.fg_warehouse,
"fg_completed_qty": pending_qty_to_produce,
"additional_costs": get_additional_costs(production_order, fg_qty=pending_qty_to_produce)
}
def get_additional_costs(production_order=None, bom_no=None, fg_qty=None):
additional_costs = []
operating_cost_per_unit = get_operating_cost_per_unit(production_order, bom_no)
if operating_cost_per_unit:
additional_costs.append({
"description": "Operating Cost as per Production Order / BOM",
"amount": operating_cost_per_unit * flt(fg_qty)
})
if production_order and production_order.additional_operating_cost:
additional_operating_cost_per_unit = \
flt(production_order.additional_operating_cost) / flt(production_order.qty)
additional_costs.append({
"description": "Additional Operating Cost",
"amount": additional_operating_cost_per_unit * flt(fg_qty)
})
return additional_costs
def get_operating_cost_per_unit(production_order=None, bom_no=None):
operating_cost_per_unit = 0
if production_order:
if not bom_no:
bom_no = production_order.bom_no
for d in production_order.get("operations"):
if flt(d.completed_qty):
operating_cost_per_unit += flt(d.actual_operating_cost) / flt(d.completed_qty)
else:
operating_cost_per_unit += flt(d.planned_operating_cost) / flt(production_order.qty)
# Get operating cost from BOM if not found in production_order.
if not operating_cost_per_unit and bom_no:
bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1)
operating_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)
return operating_cost_per_unit

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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