Merge branch 'develop' into fix/subcontracting-receipt/gl-entries

This commit is contained in:
Sagar Sharma
2022-09-07 14:55:56 +05:30
committed by GitHub
95 changed files with 1758 additions and 921 deletions

View File

@@ -107,9 +107,9 @@ frappe.ui.form.on('Subcontracting Order', {
get_materials_from_supplier: function (frm) {
let sco_rm_details = [];
if (frm.doc.supplied_items && (frm.doc.per_received == 100)) {
if (frm.doc.supplied_items && frm.doc.per_received > 0) {
frm.doc.supplied_items.forEach(d => {
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) {
sco_rm_details.push(d.name);
}
});
@@ -160,14 +160,11 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
var me = this;
if (doc.docstatus == 1) {
if (doc.status != 'Completed') {
if (!['Closed', 'Completed'].includes(doc.status)) {
if (flt(doc.per_received) < 100) {
cur_frm.add_custom_button(__('Subcontracting Receipt'), this.make_subcontracting_receipt, __('Create'));
if (me.has_unsupplied_items()) {
cur_frm.add_custom_button(__('Material to Supplier'),
() => {
me.make_stock_entry();
}, __('Transfer'));
cur_frm.add_custom_button(__('Material to Supplier'), this.make_stock_entry, __('Transfer'));
}
}
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
@@ -195,120 +192,6 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse);
}
make_stock_entry() {
var items = $.map(cur_frm.doc.items, (d) => d.bom ? d.item_code : false);
var me = this;
if (items.length >= 1) {
me.raw_material_data = [];
me.show_dialog = 1;
let title = __('Transfer Material to Supplier');
let fields = [
{ fieldtype: 'Section Break', label: __('Raw Materials') },
{
fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'),
fields: [
{
fieldtype: 'Data',
fieldname: 'item_code',
label: __('Item'),
read_only: 1,
in_list_view: 1
},
{
fieldtype: 'Data',
fieldname: 'rm_item_code',
label: __('Raw Material'),
read_only: 1,
in_list_view: 1
},
{
fieldtype: 'Float',
read_only: 1,
fieldname: 'qty',
label: __('Quantity'),
in_list_view: 1
},
{
fieldtype: 'Data',
read_only: 1,
fieldname: 'warehouse',
label: __('Reserve Warehouse'),
in_list_view: 1
},
{
fieldtype: 'Float',
read_only: 1,
fieldname: 'rate',
label: __('Rate'),
hidden: 1
},
{
fieldtype: 'Float',
read_only: 1,
fieldname: 'amount',
label: __('Amount'),
hidden: 1
},
{
fieldtype: 'Link',
read_only: 1,
fieldname: 'uom',
label: __('UOM'),
hidden: 1
}
],
data: me.raw_material_data,
get_data: () => me.raw_material_data
}
];
me.dialog = new frappe.ui.Dialog({
title: title, fields: fields
});
if (me.frm.doc['supplied_items']) {
me.frm.doc['supplied_items'].forEach((item) => {
if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) {
me.raw_material_data.push({
'name': item.name,
'item_code': item.main_item_code,
'rm_item_code': item.rm_item_code,
'item_name': item.rm_item_code,
'qty': item.required_qty - item.supplied_qty,
'warehouse': item.reserve_warehouse,
'rate': item.rate,
'amount': item.amount,
'stock_uom': item.stock_uom
});
me.dialog.fields_dict.sub_con_rm_items.grid.refresh();
}
});
}
me.dialog.get_field('sub_con_rm_items').check_all_rows();
me.dialog.show();
this.dialog.set_primary_action(__('Transfer'), () => {
me.values = me.dialog.get_values();
if (me.values) {
me.values.sub_con_rm_items.map((row, i) => {
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
let row_id = i + 1;
frappe.throw(__('Item Code, warehouse and quantity are required on row {0}', [row_id]));
}
});
me.make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children());
me.dialog.hide();
}
});
}
me.dialog.get_close_btn().on('click', () => {
me.dialog.hide();
});
}
has_unsupplied_items() {
return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
}
@@ -321,6 +204,15 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
});
}
make_stock_entry() {
frappe.model.open_mapped_doc({
method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order',
source_name: cur_frm.doc.name,
freeze: true,
freeze_message: __('Creating Stock Entry ...')
});
}
make_rm_stock_entry(rm_items) {
frappe.call({
method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry',

View File

@@ -153,7 +153,7 @@ class SubcontractingOrder(SubcontractingController):
else:
self.set_missing_values()
def update_status(self, status=None, update_modified=False):
def update_status(self, status=None, update_modified=True):
if self.docstatus >= 1 and not status:
if self.docstatus == 1:
if self.status == "Draft":
@@ -162,6 +162,10 @@ class SubcontractingOrder(SubcontractingController):
status = "Completed"
elif self.per_received > 0 and self.per_received < 100:
status = "Partially Received"
for item in self.supplied_items:
if item.returned_qty:
status = "Closed"
break
else:
total_required_qty = total_supplied_qty = 0
for item in self.supplied_items:
@@ -176,7 +180,10 @@ class SubcontractingOrder(SubcontractingController):
elif self.docstatus == 2:
status = "Cancelled"
frappe.db.set_value("Subcontracting Order", self.name, "status", status, update_modified)
if status:
frappe.db.set_value(
"Subcontracting Order", self.name, "status", status, update_modified=update_modified
)
@frappe.whitelist()

View File

@@ -10,6 +10,7 @@ frappe.listview_settings['Subcontracting Order'] = {
"Completed": "green",
"Partial Material Transferred": "purple",
"Material Transferred": "blue",
"Closed": "red",
};
return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
},

View File

@@ -7,7 +7,10 @@ import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
from erpnext.controllers.subcontracting_controller import (
get_materials_from_supplier,
make_rm_stock_entry,
)
from erpnext.controllers.tests.test_subcontracting_controller import (
get_rm_items,
get_subcontracting_order,
@@ -89,6 +92,16 @@ class TestSubcontractingOrder(FrappeTestCase):
sco.load_from_db()
self.assertEqual(sco.status, "Partially Received")
# Closed
ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
ste.save()
ste.submit()
sco.load_from_db()
self.assertEqual(sco.status, "Closed")
ste.cancel()
sco.load_from_db()
self.assertEqual(sco.status, "Partially Received")
# Completed
scr = make_subcontracting_receipt(sco.name)
scr.save()
@@ -174,22 +187,13 @@ class TestSubcontractingOrder(FrappeTestCase):
self.assertEqual(len(ste.items), len(rm_items))
def test_update_reserved_qty_for_subcontracting(self):
# Make stock available for raw materials
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
# Create RM Material Receipt
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100)
make_stock_entry(
target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100
)
make_stock_entry(
target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=30, basic_rate=100
)
make_stock_entry(
target="_Test Warehouse 1 - _TC",
item_code="_Test Item Home Desktop 100",
qty=30,
basic_rate=100,
)
bin1 = frappe.db.get_value(
bin_before_sco = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
@@ -209,102 +213,97 @@ class TestSubcontractingOrder(FrappeTestCase):
]
sco = get_subcontracting_order(service_items=service_items)
bin2 = frappe.db.get_value(
bin_after_sco = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
as_dict=1,
)
self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
self.assertNotEqual(bin1.modified, bin2.modified)
# reserved_qty_for_sub_contract should be increased by 10
self.assertEqual(
bin_after_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract + 10
)
# Create stock transfer
# projected_qty should be decreased by 10
self.assertEqual(bin_after_sco.projected_qty, bin_before_sco.projected_qty - 10)
self.assertNotEqual(bin_before_sco.modified, bin_after_sco.modified)
# Create Stock Entry(Send to Subcontractor)
rm_items = [
{
"item_code": "_Test FG Item",
"rm_item_code": "_Test Item",
"item_name": "_Test Item",
"qty": 6,
"qty": 10,
"warehouse": "_Test Warehouse - _TC",
"rate": 100,
"amount": 600,
"amount": 1000,
"stock_uom": "Nos",
}
},
{
"item_code": "_Test FG Item",
"rm_item_code": "_Test Item Home Desktop 100",
"item_name": "_Test Item Home Desktop 100",
"qty": 20,
"warehouse": "_Test Warehouse - _TC",
"rate": 100,
"amount": 2000,
"stock_uom": "Nos",
},
]
ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items))
ste.to_warehouse = "_Test Warehouse 1 - _TC"
ste.save()
ste.submit()
bin3 = frappe.db.get_value(
bin_after_rm_transfer = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract",
as_dict=1,
)
self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
make_stock_entry(
target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100
)
make_stock_entry(
target="_Test Warehouse 1 - _TC",
item_code="_Test Item Home Desktop 100",
qty=40,
basic_rate=100,
# reserved_qty_for_sub_contract should be decreased by 10
self.assertEqual(
bin_after_rm_transfer.reserved_qty_for_sub_contract,
bin_after_sco.reserved_qty_for_sub_contract - 10,
)
# Make SCR against the SCO
scr = make_subcontracting_receipt(sco.name)
scr.save()
scr.submit()
bin4 = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract",
as_dict=1,
)
self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel SCR
scr.reload()
scr.cancel()
bin5 = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract",
as_dict=1,
)
self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Cancel Stock Entry
# Cancel Stock Entry(Send to Subcontractor)
ste.cancel()
bin6 = frappe.db.get_value(
bin_after_cancel_ste = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract",
as_dict=1,
)
self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
# reserved_qty_for_sub_contract should be increased by 10
self.assertEqual(
bin_after_cancel_ste.reserved_qty_for_sub_contract,
bin_after_rm_transfer.reserved_qty_for_sub_contract + 10,
)
# Cancel PO
# Cancel SCO
sco.reload()
sco.cancel()
bin7 = frappe.db.get_value(
bin_after_cancel_sco = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract",
as_dict=1,
)
self.assertEqual(bin7.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# reserved_qty_for_sub_contract should be decreased by 10
self.assertEqual(
bin_after_cancel_sco.reserved_qty_for_sub_contract,
bin_after_cancel_ste.reserved_qty_for_sub_contract - 10,
)
self.assertEqual(
bin_after_cancel_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract
)
def test_exploded_items(self):
item_code = "_Test Subcontracted FG Item 11"

View File

@@ -150,8 +150,7 @@
"label": "Returned Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
"hidden": 1
"read_only": 1
},
{
"fieldname": "total_supplied_qty",
@@ -166,7 +165,7 @@
"hide_toolbar": 1,
"istable": 1,
"links": [],
"modified": "2022-04-07 12:58:28.208847",
"modified": "2022-08-26 16:04:56.125951",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Supplied Item",

View File

@@ -369,7 +369,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled",
"options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled\nClosed",
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
@@ -625,9 +625,10 @@
"print_hide": 1
}
],
"in_create": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-08-19 19:50:16.935124",
"modified": "2022-08-26 21:02:26.353870",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt",

View File

@@ -77,6 +77,7 @@ class SubcontractingReceipt(SubcontractingController):
self.get_current_stock()
def on_submit(self):
self.validate_available_qty_for_consumption()
self.update_status_updater_args()
self.update_prevdoc_status()
self.set_subcontracting_order_status()
@@ -109,10 +110,42 @@ class SubcontractingReceipt(SubcontractingController):
self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items()
def set_available_qty_for_consumption(self):
supplied_items_details = {}
sco_supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
for item in self.get("items"):
supplied_items = (
frappe.qb.from_(sco_supplied_item)
.select(
sco_supplied_item.rm_item_code,
sco_supplied_item.reference_name,
(sco_supplied_item.total_supplied_qty - sco_supplied_item.consumed_qty).as_("available_qty"),
)
.where(
(sco_supplied_item.parent == item.subcontracting_order)
& (sco_supplied_item.main_item_code == item.item_code)
& (sco_supplied_item.reference_name == item.subcontracting_order_item)
)
).run(as_dict=True)
if supplied_items:
supplied_items_details[item.name] = {}
for supplied_item in supplied_items:
supplied_items_details[item.name][supplied_item.rm_item_code] = supplied_item.available_qty
else:
for item in self.get("supplied_items"):
item.available_qty_for_consumption = supplied_items_details.get(item.reference_name, {}).get(
item.rm_item_code, 0
)
def set_missing_values_in_supplied_items(self):
for item in self.get("supplied_items") or []:
item.amount = item.rate * item.consumed_qty
self.set_available_qty_for_consumption()
def set_missing_values_in_items(self):
rm_supp_cost = {}
for item in self.get("supplied_items") or []:
@@ -149,6 +182,17 @@ class SubcontractingReceipt(SubcontractingController):
_("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code)
)
def validate_available_qty_for_consumption(self):
for item in self.get("supplied_items"):
if (
item.available_qty_for_consumption and item.available_qty_for_consumption < item.consumed_qty
):
frappe.throw(
_(
"Row {0}: Consumed Qty must be less than or equal to Available Qty For Consumption in Consumed Items Table."
).format(item.idx)
)
def set_items_cost_center(self):
if self.company:
cost_center = frappe.get_cached_value("Company", self.company, "cost_center")

View File

@@ -73,6 +73,55 @@ class TestSubcontractingReceipt(FrappeTestCase):
rm_supp_cost = sum(item.amount for item in scr.get("supplied_items"))
self.assertEqual(scr.get("items")[0].rm_supp_cost, flt(rm_supp_cost))
def test_available_qty_for_consumption(self):
make_stock_entry(
item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100
)
make_stock_entry(
item_code="_Test Item Home Desktop 100",
qty=100,
target="_Test Warehouse 1 - _TC",
basic_rate=100,
)
service_items = [
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Service Item 1",
"qty": 10,
"rate": 100,
"fg_item": "_Test FG Item",
"fg_item_qty": 10,
},
]
sco = get_subcontracting_order(service_items=service_items)
rm_items = [
{
"main_item_code": "_Test FG Item",
"item_code": "_Test Item",
"qty": 5.0,
"rate": 100.0,
"stock_uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC",
},
{
"main_item_code": "_Test FG Item",
"item_code": "_Test Item Home Desktop 100",
"qty": 10.0,
"rate": 100.0,
"stock_uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC",
},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
make_stock_transfer_entry(
sco_no=sco.name,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
scr = make_subcontracting_receipt(sco.name)
scr.save()
self.assertRaises(frappe.ValidationError, scr.submit)
def test_subcontracting_gle_fg_item_rate_zero(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries

View File

@@ -19,6 +19,7 @@
"col_break2",
"amount",
"secbreak_2",
"available_qty_for_consumption",
"required_qty",
"col_break3",
"consumed_qty",
@@ -75,8 +76,7 @@
{
"fieldname": "required_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Available Qty For Consumption",
"label": "Required Qty",
"print_hide": 1,
"read_only": 1
},
@@ -85,7 +85,7 @@
"fieldname": "consumed_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty to be Consumed",
"label": "Consumed Qty",
"reqd": 1
},
{
@@ -179,12 +179,21 @@
"options": "Subcontracting Order",
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "available_qty_for_consumption",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Available Qty For Consumption",
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-04-18 10:45:16.538479",
"modified": "2022-09-02 22:28:53.392381",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Supplied Item",
@@ -193,6 +202,6 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"states": []
"states": [],
"track_changes": 1
}