Merge pull request #32089 from frappe/mergify/bp/develop/pr-32086
fix: QR Code multi currency issue (backport #32086)
diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml
new file mode 100644
index 0000000..ef38974
--- /dev/null
+++ b/.github/workflows/initiate_release.yml
@@ -0,0 +1,32 @@
+# This workflow is agnostic to branches. Only maintain on develop branch.
+# To add/remove versions just modify the matrix.
+
+name: Create weekly release pull requests
+on:
+ schedule:
+ # 9:30 UTC => 3 PM IST Tuesday
+ - cron: "30 9 * * 2"
+ workflow_dispatch:
+
+jobs:
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ version: ["13", "14"]
+
+ steps:
+ - uses: octokit/request-action@v2.x
+ with:
+ route: POST /repos/{owner}/{repo}/pulls
+ owner: frappe
+ repo: erpnext
+ title: |-
+ "chore: release v${{ matrix.version }}"
+ body: "Automated weekly release."
+ base: version-${{ matrix.version }}
+ head: version-${{ matrix.version }}-hotfix
+ env:
+ GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index a3d41ab..5e9c069 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -311,6 +311,7 @@
"sales_invoice_item": d.get("sales_invoice_item"),
"dn_detail": d.get("dn_detail"),
"incoming_rate": p.get("incoming_rate"),
+ "item_row": p,
}
)
)
@@ -334,6 +335,7 @@
"sales_invoice_item": d.get("sales_invoice_item"),
"dn_detail": d.get("dn_detail"),
"incoming_rate": d.get("incoming_rate"),
+ "item_row": d,
}
)
)
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 36bed36..d4f9aba 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -390,6 +390,10 @@
return sl_dict
def update_inventory_dimensions(self, row, sl_dict) -> None:
+ # To handle delivery note and sales invoice
+ if row.get("item_row"):
+ row = row.get("item_row")
+
dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
for dimension in dimensions:
if not dimension:
@@ -407,9 +411,17 @@
"DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
)
+ if not fieldname:
+ fieldname = frappe.get_cached_value(
+ "Custom Field", {"dt": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
+ )
+
if fieldname and self.get(fieldname):
sl_dict[dimension.target_fieldname] = self.get(fieldname)
+ if sl_dict[dimension.target_fieldname] and self.docstatus == 1:
+ row.db_set(dimension.source_fieldname, sl_dict[dimension.target_fieldname])
+
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries
diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json
index 7a806d5..39e0acd 100644
--- a/erpnext/setup/doctype/employee/employee.json
+++ b/erpnext/setup/doctype/employee/employee.json
@@ -10,79 +10,89 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "basic_details_tab",
"basic_information",
"employee",
"naming_series",
"first_name",
"middle_name",
"last_name",
- "salutation",
"employee_name",
- "image",
- "column_break1",
- "company",
- "status",
+ "column_break_9",
"gender",
"date_of_birth",
+ "salutation",
+ "column_break1",
"date_of_joining",
- "employee_number",
- "emergency_contact_details",
- "person_to_be_contacted",
- "relation",
- "column_break_19",
- "emergency_phone_number",
+ "image",
+ "status",
"erpnext_user",
"user_id",
"create_user",
"create_user_permission",
- "employment_details",
- "scheduled_confirmation_date",
- "final_confirmation_date",
- "col_break_22",
- "contract_end_date",
- "notice_number_of_days",
- "date_of_retirement",
- "job_profile",
+ "company_details_section",
+ "company",
"department",
+ "employee_number",
+ "column_break_25",
"designation",
"reports_to",
- "column_break_31",
+ "column_break_18",
"branch",
+ "employment_details",
+ "scheduled_confirmation_date",
+ "column_break_32",
+ "final_confirmation_date",
+ "contract_end_date",
+ "col_break_22",
+ "notice_number_of_days",
+ "date_of_retirement",
+ "contact_details",
+ "cell_number",
+ "column_break_40",
+ "personal_email",
+ "company_email",
+ "column_break4",
+ "prefered_contact_email",
+ "prefered_email",
+ "unsubscribed",
+ "address_section",
+ "current_address",
+ "current_accommodation_type",
+ "column_break_46",
+ "permanent_address",
+ "permanent_accommodation_type",
+ "emergency_contact_details",
+ "person_to_be_contacted",
+ "column_break_55",
+ "emergency_phone_number",
+ "column_break_19",
+ "relation",
"attendance_and_leave_details",
"attendance_device_id",
"column_break_44",
"holiday_list",
"salary_information",
- "salary_currency",
"ctc",
- "payroll_cost_center",
- "column_break_52",
+ "salary_currency",
+ "salary_mode",
+ "bank_details_section",
"bank_name",
"bank_ac_no",
- "contact_details",
- "cell_number",
- "prefered_email",
- "personal_email",
- "unsubscribed",
- "permanent_accommodation_type",
- "permanent_address",
- "column_break4",
- "prefered_contact_email",
- "company_email",
- "current_accommodation_type",
- "current_address",
- "sb53",
- "bio",
"personal_details",
- "passport_number",
- "date_of_issue",
- "valid_upto",
- "place_of_issue",
"marital_status",
- "blood_group",
- "column_break6",
"family_background",
+ "column_break6",
+ "blood_group",
"health_details",
+ "passport_details_section",
+ "passport_number",
+ "valid_upto",
+ "column_break_73",
+ "date_of_issue",
+ "place_of_issue",
+ "profile_tab",
+ "bio",
"educational_qualification",
"education",
"previous_work_experience",
@@ -92,16 +102,20 @@
"exit",
"resignation_letter_date",
"relieving_date",
- "reason_for_leaving",
- "leave_encashed",
- "encashment_date",
"exit_interview_details",
"held_on",
"new_workplace",
+ "column_break_99",
+ "leave_encashed",
+ "encashment_date",
+ "feedback_section",
+ "reason_for_leaving",
+ "column_break_104",
"feedback",
"lft",
"rgt",
- "old_parent"
+ "old_parent",
+ "connections_tab"
],
"fields": [
{
@@ -261,7 +275,7 @@
"collapsible": 1,
"fieldname": "erpnext_user",
"fieldtype": "Section Break",
- "label": "ERPNext User"
+ "label": "User Details"
},
{
"description": "System User (login) ID. If set, it will become default for all HR forms.",
@@ -289,8 +303,8 @@
"allow_in_quick_entry": 1,
"collapsible": 1,
"fieldname": "employment_details",
- "fieldtype": "Section Break",
- "label": "Joining Details"
+ "fieldtype": "Tab Break",
+ "label": "Joining"
},
{
"fieldname": "scheduled_confirmation_date",
@@ -332,12 +346,6 @@
"oldfieldtype": "Date"
},
{
- "collapsible": 1,
- "fieldname": "job_profile",
- "fieldtype": "Section Break",
- "label": "Department"
- },
- {
"fieldname": "department",
"fieldtype": "Link",
"in_standard_filter": 1,
@@ -367,10 +375,6 @@
"options": "Employee"
},
{
- "fieldname": "column_break_31",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "branch",
"fieldtype": "Link",
"label": "Branch",
@@ -391,7 +395,7 @@
{
"collapsible": 1,
"fieldname": "salary_information",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Salary Details",
"oldfieldtype": "Section Break",
"width": "50%"
@@ -423,8 +427,8 @@
{
"collapsible": 1,
"fieldname": "contact_details",
- "fieldtype": "Section Break",
- "label": "Contact Details"
+ "fieldtype": "Tab Break",
+ "label": "Contact"
},
{
"fieldname": "cell_number",
@@ -494,12 +498,6 @@
"label": "Current Address"
},
{
- "collapsible": 1,
- "fieldname": "sb53",
- "fieldtype": "Section Break",
- "label": "Personal Bio"
- },
- {
"description": "Short biography for website and other publications.",
"fieldname": "bio",
"fieldtype": "Text Editor",
@@ -508,7 +506,7 @@
{
"collapsible": 1,
"fieldname": "personal_details",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Personal Details"
},
{
@@ -601,7 +599,7 @@
{
"collapsible": 1,
"fieldname": "exit",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Exit",
"oldfieldtype": "Section Break"
},
@@ -702,7 +700,7 @@
{
"collapsible": 1,
"fieldname": "attendance_and_leave_details",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Attendance and Leave Details"
},
{
@@ -714,10 +712,6 @@
"fieldtype": "Column Break"
},
{
- "fieldname": "column_break_52",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "salary_currency",
"fieldtype": "Link",
"label": "Salary Currency",
@@ -728,13 +722,95 @@
"fieldtype": "Currency",
"label": "Cost to Company (CTC)",
"options": "salary_currency"
+ },
+ {
+ "fieldname": "basic_details_tab",
+ "fieldtype": "Tab Break",
+ "label": "Basic Details"
+ },
+ {
+ "fieldname": "company_details_section",
+ "fieldtype": "Section Break",
+ "label": "Company Details"
+ },
+ {
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "address_section",
+ "fieldtype": "Section Break",
+ "label": "Address"
+ },
+ {
+ "fieldname": "column_break_46",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "profile_tab",
+ "fieldtype": "Tab Break",
+ "label": "Profile"
+ },
+ {
+ "fieldname": "passport_details_section",
+ "fieldtype": "Section Break",
+ "label": "Passport Details"
+ },
+ {
+ "fieldname": "column_break_73",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "bank_details_section",
+ "fieldtype": "Section Break",
+ "label": "Bank Details"
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_25",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "connections_tab",
+ "fieldtype": "Tab Break",
+ "label": "Connections",
+ "show_dashboard": 1
+ },
+ {
+ "fieldname": "column_break_32",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_40",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_55",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_99",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "feedback_section",
+ "fieldtype": "Section Break",
+ "label": "Feedback"
+ },
+ {
+ "fieldname": "column_break_104",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2022-06-27 01:29:32.952091",
+ "modified": "2022-08-23 13:47:46.944993",
"modified_by": "Administrator",
"module": "Setup",
"name": "Employee",
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
index 07cb73b..79e7895 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
@@ -30,6 +30,7 @@
onload(frm) {
frm.trigger('render_traget_field');
+ frm.trigger("set_parent_fields");
},
refresh(frm) {
@@ -52,6 +53,30 @@
}
},
+ document_type(frm) {
+ frm.trigger("set_parent_fields");
+ },
+
+ set_parent_fields(frm) {
+ if (frm.doc.apply_to_all_doctypes) {
+ frm.set_df_property("fetch_from_parent", "options", frm.doc.reference_document);
+ } else if (frm.doc.document_type && frm.doc.istable) {
+ frappe.call({
+ method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields',
+ args: {
+ child_doctype: frm.doc.document_type,
+ dimension_name: frm.doc.reference_document
+ },
+ callback: (r) => {
+ if (r.message && r.message.length) {
+ frm.set_df_property("fetch_from_parent", "options",
+ [""].concat(r.message));
+ }
+ }
+ });
+ }
+ },
+
delete_dimension(frm) {
let msg = (`
Custom fields related to this dimension will be deleted on deletion of dimension.
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
index 03e7fda..09f4f63 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
@@ -144,16 +144,15 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "istable",
"description": "Set fieldname or DocType name like Supplier, Customer etc.",
"fieldname": "fetch_from_parent",
- "fieldtype": "Data",
+ "fieldtype": "Select",
"label": "Fetch Value From Parent Form"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-08-17 11:43:24.722441",
+ "modified": "2022-09-02 13:29:04.098469",
"modified_by": "Administrator",
"module": "Stock",
"name": "Inventory Dimension",
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
index 4ff8f33..9e8c10b 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
@@ -236,3 +236,30 @@
def delete_dimension(dimension):
doc = frappe.get_doc("Inventory Dimension", dimension)
doc.delete()
+
+
+@frappe.whitelist()
+def get_parent_fields(child_doctype, dimension_name):
+ parent_doctypes = frappe.get_all(
+ "DocField", fields=["parent"], filters={"options": child_doctype}
+ )
+
+ fields = []
+
+ fields.extend(
+ frappe.get_all(
+ "DocField",
+ fields=["fieldname as value", "label"],
+ filters={"options": dimension_name, "parent": ("in", [d.parent for d in parent_doctypes])},
+ )
+ )
+
+ fields.extend(
+ frappe.get_all(
+ "Custom Field",
+ fields=["fieldname as value", "label"],
+ filters={"options": dimension_name, "dt": ("in", [d.parent for d in parent_doctypes])},
+ )
+ )
+
+ return fields
diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
index cc90b74..19ddc44 100644
--- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
@@ -2,14 +2,17 @@
# See license.txt
import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.tests.utils import FrappeTestCase
+from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
CanNotBeChildDoc,
CanNotBeDefaultDimension,
DoNotChangeError,
delete_dimension,
)
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -136,6 +139,58 @@
self.assertTrue(inv_dim1.has_stock_ledger())
self.assertRaises(DoNotChangeError, inv_dim1.save)
+ def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
+ create_inventory_dimension(
+ reference_document="Rack",
+ type_of_transaction="Both",
+ dimension_name="Rack",
+ apply_to_all_doctypes=1,
+ fetch_from_parent="Rack",
+ )
+
+ create_custom_field(
+ "Purchase Receipt", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack")
+ )
+
+ create_custom_field(
+ "Delivery Note", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack")
+ )
+
+ frappe.reload_doc("stock", "doctype", "purchase_receipt_item")
+ frappe.reload_doc("stock", "doctype", "delivery_note_item")
+
+ pr_doc = make_purchase_receipt(qty=2, do_not_submit=True)
+ pr_doc.rack = "Rack 1"
+ pr_doc.save()
+ pr_doc.submit()
+
+ pr_doc.load_from_db()
+
+ self.assertEqual(pr_doc.items[0].rack, "Rack 1")
+ sle_rack = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_detail_no": pr_doc.items[0].name, "voucher_type": pr_doc.doctype},
+ "rack",
+ )
+
+ self.assertEqual(sle_rack, "Rack 1")
+
+ dn_doc = create_delivery_note(qty=2, do_not_submit=True)
+ dn_doc.rack = "Rack 1"
+ dn_doc.save()
+ dn_doc.submit()
+
+ dn_doc.load_from_db()
+
+ self.assertEqual(dn_doc.items[0].rack, "Rack 1")
+ sle_rack = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_detail_no": dn_doc.items[0].name, "voucher_type": dn_doc.doctype},
+ "rack",
+ )
+
+ self.assertEqual(sle_rack, "Rack 1")
+
def prepare_test_data():
if not frappe.db.exists("DocType", "Shelf"):
@@ -160,6 +215,28 @@
create_warehouse("Shelf Warehouse")
+ if not frappe.db.exists("DocType", "Rack"):
+ frappe.get_doc(
+ {
+ "doctype": "DocType",
+ "name": "Rack",
+ "module": "Stock",
+ "custom": 1,
+ "naming_rule": "By fieldname",
+ "autoname": "field:rack_name",
+ "fields": [{"label": "Rack Name", "fieldname": "rack_name", "fieldtype": "Data"}],
+ "permissions": [
+ {"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
+ ],
+ }
+ ).insert(ignore_permissions=True)
+
+ for rack in ["Rack 1"]:
+ if not frappe.db.exists("Rack", rack):
+ frappe.get_doc({"doctype": "Rack", "rack_name": rack}).insert(ignore_permissions=True)
+
+ create_warehouse("Rack Warehouse")
+
def create_inventory_dimension(**args):
args = frappe._dict(args)
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index 098242a..f4a943f 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -187,22 +187,13 @@
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"],
@@ -222,102 +213,97 @@
]
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"
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 021d9aa..1da7340 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -75,6 +75,7 @@
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()
@@ -107,10 +108,42 @@
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 []:
@@ -147,6 +180,17 @@
_("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")
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 763e768..a47af52 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -70,6 +70,55 @@
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
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
index 100a806..ddbb806 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
@@ -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
}
\ No newline at end of file