mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-03 04:09:11 +00:00
Merge pull request #44640 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -208,13 +208,17 @@ class BankTransaction(Document):
|
|||||||
if self.party_type and self.party:
|
if self.party_type and self.party:
|
||||||
return
|
return
|
||||||
|
|
||||||
result = AutoMatchParty(
|
result = None
|
||||||
bank_party_account_number=self.bank_party_account_number,
|
try:
|
||||||
bank_party_iban=self.bank_party_iban,
|
result = AutoMatchParty(
|
||||||
bank_party_name=self.bank_party_name,
|
bank_party_account_number=self.bank_party_account_number,
|
||||||
description=self.description,
|
bank_party_iban=self.bank_party_iban,
|
||||||
deposit=self.deposit,
|
bank_party_name=self.bank_party_name,
|
||||||
).match()
|
description=self.description,
|
||||||
|
deposit=self.deposit,
|
||||||
|
).match()
|
||||||
|
except Exception:
|
||||||
|
frappe.log_error(title=_("Error in party matching for Bank Transaction {0}").format(self.name))
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -153,10 +153,7 @@ class PaymentReconciliation(Document):
|
|||||||
self.add_payment_entries(non_reconciled_payments)
|
self.add_payment_entries(non_reconciled_payments)
|
||||||
|
|
||||||
def get_payment_entries(self):
|
def get_payment_entries(self):
|
||||||
if self.default_advance_account:
|
party_account = [self.receivable_payable_account]
|
||||||
party_account = [self.receivable_payable_account, self.default_advance_account]
|
|
||||||
else:
|
|
||||||
party_account = [self.receivable_payable_account]
|
|
||||||
|
|
||||||
order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order"
|
order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order"
|
||||||
condition = frappe._dict(
|
condition = frappe._dict(
|
||||||
@@ -187,6 +184,7 @@ class PaymentReconciliation(Document):
|
|||||||
self.party,
|
self.party,
|
||||||
party_account,
|
party_account,
|
||||||
order_doctype,
|
order_doctype,
|
||||||
|
default_advance_account=self.default_advance_account,
|
||||||
against_all_orders=True,
|
against_all_orders=True,
|
||||||
limit=self.payment_limit,
|
limit=self.payment_limit,
|
||||||
condition=condition,
|
condition=condition,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const INDICATORS = {
|
const INDICATORS = {
|
||||||
"Partially Paid": "orange",
|
"Partially Paid": "orange",
|
||||||
Cancelled: "red",
|
Cancelled: "red",
|
||||||
Draft: "gray",
|
Draft: "red",
|
||||||
Failed: "red",
|
Failed: "red",
|
||||||
Initiated: "green",
|
Initiated: "green",
|
||||||
Paid: "blue",
|
Paid: "blue",
|
||||||
|
|||||||
@@ -651,8 +651,17 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
|||||||
|
|
||||||
qty = pricing_rule.free_qty or 1
|
qty = pricing_rule.free_qty or 1
|
||||||
if pricing_rule.is_recursive:
|
if pricing_rule.is_recursive:
|
||||||
transaction_qty = (args.get("qty") if args else doc.total_qty) - pricing_rule.apply_recursion_over
|
transaction_qty = sum(
|
||||||
if transaction_qty:
|
[
|
||||||
|
row.qty
|
||||||
|
for row in doc.items
|
||||||
|
if not row.is_free_item
|
||||||
|
and row.item_code == args.item_code
|
||||||
|
and row.pricing_rules == args.pricing_rules
|
||||||
|
]
|
||||||
|
)
|
||||||
|
transaction_qty = transaction_qty - pricing_rule.apply_recursion_over
|
||||||
|
if transaction_qty and transaction_qty > 0:
|
||||||
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
|
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
|
||||||
if pricing_rule.round_free_qty:
|
if pricing_rule.round_free_qty:
|
||||||
qty = (flt(transaction_qty) // pricing_rule.recurse_for) * (pricing_rule.free_qty or 1)
|
qty = (flt(transaction_qty) // pricing_rule.recurse_for) * (pricing_rule.free_qty or 1)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ frappe.listview_settings["Sales Invoice"] = {
|
|||||||
],
|
],
|
||||||
get_indicator: function (doc) {
|
get_indicator: function (doc) {
|
||||||
const status_colors = {
|
const status_colors = {
|
||||||
Draft: "grey",
|
Draft: "red",
|
||||||
Unpaid: "orange",
|
Unpaid: "orange",
|
||||||
Paid: "green",
|
Paid: "green",
|
||||||
Return: "gray",
|
Return: "gray",
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ def execute(filters=None):
|
|||||||
if filters.get("party"):
|
if filters.get("party"):
|
||||||
filters.party = frappe.parse_json(filters.get("party"))
|
filters.party = frappe.parse_json(filters.get("party"))
|
||||||
|
|
||||||
if filters.get("voucher_no") and not filters.get("group_by"):
|
|
||||||
filters.group_by = "Group by Voucher (Consolidated)"
|
|
||||||
|
|
||||||
validate_filters(filters, account_details)
|
validate_filters(filters, account_details)
|
||||||
|
|
||||||
validate_party(filters)
|
validate_party(filters)
|
||||||
@@ -373,16 +370,21 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
|
|||||||
if acc_dict.entries:
|
if acc_dict.entries:
|
||||||
# opening
|
# opening
|
||||||
data.append({"debit_in_transaction_currency": None, "credit_in_transaction_currency": None})
|
data.append({"debit_in_transaction_currency": None, "credit_in_transaction_currency": None})
|
||||||
if filters.get("group_by") != "Group by Voucher":
|
if (not filters.get("group_by") and not filters.get("voucher_no")) or (
|
||||||
|
filters.get("group_by") and filters.get("group_by") != "Group by Voucher"
|
||||||
|
):
|
||||||
data.append(acc_dict.totals.opening)
|
data.append(acc_dict.totals.opening)
|
||||||
|
|
||||||
data += acc_dict.entries
|
data += acc_dict.entries
|
||||||
|
|
||||||
# totals
|
# totals
|
||||||
data.append(acc_dict.totals.total)
|
if filters.get("group_by") or not filters.voucher_no:
|
||||||
|
data.append(acc_dict.totals.total)
|
||||||
|
|
||||||
# closing
|
# closing
|
||||||
if filters.get("group_by") != "Group by Voucher":
|
if (not filters.get("group_by") and not filters.get("voucher_no")) or (
|
||||||
|
filters.get("group_by") and filters.get("group_by") != "Group by Voucher"
|
||||||
|
):
|
||||||
data.append(acc_dict.totals.closing)
|
data.append(acc_dict.totals.closing)
|
||||||
|
|
||||||
data.append({"debit_in_transaction_currency": None, "credit_in_transaction_currency": None})
|
data.append({"debit_in_transaction_currency": None, "credit_in_transaction_currency": None})
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ frappe.ui.form.on("Asset", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frm.dashboard.render_graph({
|
frm.dashboard.render_graph({
|
||||||
title: "Asset Value",
|
title: __("Asset Value"),
|
||||||
data: {
|
data: {
|
||||||
labels: x_intervals,
|
labels: x_intervals,
|
||||||
datasets: [
|
datasets: [
|
||||||
|
|||||||
@@ -410,6 +410,9 @@ class Asset(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_asset_finance_books(self, row):
|
def validate_asset_finance_books(self, row):
|
||||||
|
row.expected_value_after_useful_life = flt(
|
||||||
|
row.expected_value_after_useful_life, self.precision("gross_purchase_amount")
|
||||||
|
)
|
||||||
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
|
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount").format(
|
_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount").format(
|
||||||
@@ -430,7 +433,10 @@ class Asset(AccountsController):
|
|||||||
self.opening_accumulated_depreciation = 0
|
self.opening_accumulated_depreciation = 0
|
||||||
self.opening_number_of_booked_depreciations = 0
|
self.opening_number_of_booked_depreciations = 0
|
||||||
else:
|
else:
|
||||||
depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
|
depreciable_amount = flt(
|
||||||
|
flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life),
|
||||||
|
self.precision("gross_purchase_amount"),
|
||||||
|
)
|
||||||
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
|
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Opening Accumulated Depreciation must be less than or equal to {0}").format(
|
_("Opening Accumulated Depreciation must be less than or equal to {0}").format(
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ def prepare_data(data, filters):
|
|||||||
|
|
||||||
|
|
||||||
def prepare_chart_data(pending, completed):
|
def prepare_chart_data(pending, completed):
|
||||||
labels = ["Amount to Bill", "Billed Amount"]
|
labels = [_("Amount to Bill"), _("Billed Amount")]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"data": {"labels": labels, "datasets": [{"values": [pending, completed]}]},
|
"data": {"labels": labels, "datasets": [{"values": [pending, completed]}]},
|
||||||
|
|||||||
@@ -2916,6 +2916,7 @@ def get_advance_payment_entries(
|
|||||||
party_account,
|
party_account,
|
||||||
order_doctype,
|
order_doctype,
|
||||||
order_list=None,
|
order_list=None,
|
||||||
|
default_advance_account=None,
|
||||||
include_unallocated=True,
|
include_unallocated=True,
|
||||||
against_all_orders=False,
|
against_all_orders=False,
|
||||||
limit=None,
|
limit=None,
|
||||||
@@ -2929,6 +2930,7 @@ def get_advance_payment_entries(
|
|||||||
party_type,
|
party_type,
|
||||||
party,
|
party,
|
||||||
party_account,
|
party_account,
|
||||||
|
default_advance_account,
|
||||||
limit,
|
limit,
|
||||||
condition,
|
condition,
|
||||||
)
|
)
|
||||||
@@ -2952,6 +2954,7 @@ def get_advance_payment_entries(
|
|||||||
party_type,
|
party_type,
|
||||||
party,
|
party,
|
||||||
party_account,
|
party_account,
|
||||||
|
default_advance_account,
|
||||||
limit,
|
limit,
|
||||||
condition,
|
condition,
|
||||||
)
|
)
|
||||||
@@ -2967,6 +2970,7 @@ def get_common_query(
|
|||||||
party_type,
|
party_type,
|
||||||
party,
|
party,
|
||||||
party_account,
|
party_account,
|
||||||
|
default_advance_account,
|
||||||
limit,
|
limit,
|
||||||
condition,
|
condition,
|
||||||
):
|
):
|
||||||
@@ -2988,14 +2992,22 @@ def get_common_query(
|
|||||||
.where(payment_entry.docstatus == 1)
|
.where(payment_entry.docstatus == 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
if payment_type == "Receive":
|
field = "paid_from" if payment_type == "Receive" else "paid_to"
|
||||||
q = q.select((payment_entry.paid_from_account_currency).as_("currency"))
|
|
||||||
q = q.select(payment_entry.paid_from)
|
q = q.select((payment_entry[f"{field}_account_currency"]).as_("currency"))
|
||||||
q = q.where(payment_entry.paid_from.isin(party_account))
|
q = q.select(payment_entry[field])
|
||||||
|
account_condition = payment_entry[field].isin(party_account)
|
||||||
|
if default_advance_account:
|
||||||
|
q = q.where(
|
||||||
|
account_condition
|
||||||
|
| (
|
||||||
|
(payment_entry[field] == default_advance_account)
|
||||||
|
& (payment_entry.book_advance_payments_in_separate_party_account == 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
q = q.select((payment_entry.paid_to_account_currency).as_("currency"))
|
q = q.where(account_condition)
|
||||||
q = q.select(payment_entry.paid_to)
|
|
||||||
q = q.where(payment_entry.paid_to.isin(party_account))
|
|
||||||
|
|
||||||
if payment_type == "Receive":
|
if payment_type == "Receive":
|
||||||
q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate"))
|
q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate"))
|
||||||
|
|||||||
@@ -291,6 +291,13 @@ frappe.ui.form.on("BOM", {
|
|||||||
cur_dialog.refresh();
|
cur_dialog.refresh();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fields.push({
|
||||||
|
fieldtype: "Check",
|
||||||
|
label: __("Use Multi-Level BOM"),
|
||||||
|
fieldname: "use_multi_level_bom",
|
||||||
|
default: frm.doc?.__onload.use_multi_level_bom,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var has_template_rm = frm.doc.items.filter((d) => d.has_variants === 1) || [];
|
var has_template_rm = frm.doc.items.filter((d) => d.has_variants === 1) || [];
|
||||||
|
|||||||
@@ -214,6 +214,23 @@ class BOM(WebsiteGenerator):
|
|||||||
|
|
||||||
return index
|
return index
|
||||||
|
|
||||||
|
def onload(self):
|
||||||
|
super().onload()
|
||||||
|
|
||||||
|
self.set_onload_for_muulti_level_bom()
|
||||||
|
|
||||||
|
def set_onload_for_muulti_level_bom(self):
|
||||||
|
use_multi_level_bom = frappe.db.get_value(
|
||||||
|
"Property Setter",
|
||||||
|
{"field_name": "use_multi_level_bom", "doc_type": "Work Order", "property": "default"},
|
||||||
|
"value",
|
||||||
|
)
|
||||||
|
|
||||||
|
if use_multi_level_bom is None:
|
||||||
|
use_multi_level_bom = 1
|
||||||
|
|
||||||
|
self.set_onload("use_multi_level_bom", cint(use_multi_level_bom))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_next_version_index(existing_boms: list[str]) -> int:
|
def get_next_version_index(existing_boms: list[str]) -> int:
|
||||||
# split by "/" and "-"
|
# split by "/" and "-"
|
||||||
@@ -259,6 +276,24 @@ class BOM(WebsiteGenerator):
|
|||||||
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
|
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
|
||||||
self.set_process_loss_qty()
|
self.set_process_loss_qty()
|
||||||
self.validate_scrap_items()
|
self.validate_scrap_items()
|
||||||
|
self.set_default_uom()
|
||||||
|
|
||||||
|
def set_default_uom(self):
|
||||||
|
if not self.get("items"):
|
||||||
|
return
|
||||||
|
|
||||||
|
item_wise_uom = frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
"Item",
|
||||||
|
filters={"name": ("in", [item.item_code for item in self.items])},
|
||||||
|
fields=["name", "stock_uom"],
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in self.get("items"):
|
||||||
|
if row.stock_uom != item_wise_uom.get(row.item_code):
|
||||||
|
row.stock_uom = item_wise_uom.get(row.item_code)
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
context.parents = [{"name": "boms", "title": _("All BOMs")}]
|
context.parents = [{"name": "boms", "title": _("All BOMs")}]
|
||||||
|
|||||||
@@ -755,6 +755,26 @@ class TestBOM(FrappeTestCase):
|
|||||||
self.assertTrue("_Test RM Item 2 Fixed Asset Item" not in items)
|
self.assertTrue("_Test RM Item 2 Fixed Asset Item" not in items)
|
||||||
self.assertTrue("_Test RM Item 3 Manufacture Item" in items)
|
self.assertTrue("_Test RM Item 3 Manufacture Item" in items)
|
||||||
|
|
||||||
|
def test_bom_raw_materials_stock_uom(self):
|
||||||
|
rm_item = make_item(
|
||||||
|
properties={"is_stock_item": 1, "valuation_rate": 1000.0, "stock_uom": "Nos"}
|
||||||
|
).name
|
||||||
|
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
|
bom = make_bom(item=fg_item, raw_materials=[rm_item], do_not_submit=True)
|
||||||
|
for row in bom.items:
|
||||||
|
self.assertEqual(row.stock_uom, "Nos")
|
||||||
|
|
||||||
|
frappe.db.set_value("Item", rm_item, "stock_uom", "Kg")
|
||||||
|
|
||||||
|
bom.items[0].qty = 2
|
||||||
|
bom.save()
|
||||||
|
|
||||||
|
for row in bom.items:
|
||||||
|
self.assertEqual(row.stock_uom, "Kg")
|
||||||
|
|
||||||
|
|
||||||
def get_default_bom(item_code="_Test FG Item 2"):
|
def get_default_bom(item_code="_Test FG Item 2"):
|
||||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||||
|
|||||||
@@ -243,7 +243,7 @@
|
|||||||
"depends_on": "eval:!doc.__islocal",
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"fieldname": "download_materials_required",
|
"fieldname": "download_materials_required",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Download Materials Request Plan"
|
"label": "Download Required Materials"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "get_items_for_mr",
|
"fieldname": "get_items_for_mr",
|
||||||
@@ -398,7 +398,7 @@
|
|||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "download_materials_request_plan_section_section",
|
"fieldname": "download_materials_request_plan_section_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Download Materials Request Plan Section"
|
"label": "Preview Required Materials"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -439,7 +439,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-02-27 13:34:20.692211",
|
"modified": "2024-12-04 11:55:03.108971",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
@@ -463,4 +463,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"states": []
|
"states": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,7 @@ class ProductionPlan(Document):
|
|||||||
from erpnext.manufacturing.doctype.material_request_plan_item.material_request_plan_item import (
|
from erpnext.manufacturing.doctype.material_request_plan_item.material_request_plan_item import (
|
||||||
MaterialRequestPlanItem,
|
MaterialRequestPlanItem,
|
||||||
)
|
)
|
||||||
from erpnext.manufacturing.doctype.production_plan_item.production_plan_item import (
|
from erpnext.manufacturing.doctype.production_plan_item.production_plan_item import ProductionPlanItem
|
||||||
ProductionPlanItem,
|
|
||||||
)
|
|
||||||
from erpnext.manufacturing.doctype.production_plan_item_reference.production_plan_item_reference import (
|
from erpnext.manufacturing.doctype.production_plan_item_reference.production_plan_item_reference import (
|
||||||
ProductionPlanItemReference,
|
ProductionPlanItemReference,
|
||||||
)
|
)
|
||||||
@@ -1085,24 +1083,33 @@ def download_raw_materials(doc, warehouses=None):
|
|||||||
frappe.flags.show_qty_in_stock_uom = 1
|
frappe.flags.show_qty_in_stock_uom = 1
|
||||||
items = get_items_for_material_requests(doc, warehouses=warehouses, get_parent_warehouse_data=True)
|
items = get_items_for_material_requests(doc, warehouses=warehouses, get_parent_warehouse_data=True)
|
||||||
|
|
||||||
|
duplicate_item_wh_list = frappe._dict()
|
||||||
|
|
||||||
for d in items:
|
for d in items:
|
||||||
item_list.append(
|
key = (d.get("item_code"), d.get("warehouse"))
|
||||||
[
|
if key in duplicate_item_wh_list:
|
||||||
d.get("item_code"),
|
rm_data = duplicate_item_wh_list[key]
|
||||||
d.get("item_name"),
|
rm_data[12] += d.get("quantity")
|
||||||
d.get("description"),
|
continue
|
||||||
d.get("stock_uom"),
|
|
||||||
d.get("warehouse"),
|
rm_data = [
|
||||||
d.get("required_bom_qty"),
|
d.get("item_code"),
|
||||||
d.get("projected_qty"),
|
d.get("item_name"),
|
||||||
d.get("actual_qty"),
|
d.get("description"),
|
||||||
d.get("ordered_qty"),
|
d.get("stock_uom"),
|
||||||
d.get("planned_qty"),
|
d.get("warehouse"),
|
||||||
d.get("reserved_qty_for_production"),
|
d.get("required_bom_qty"),
|
||||||
d.get("safety_stock"),
|
d.get("projected_qty"),
|
||||||
d.get("quantity"),
|
d.get("actual_qty"),
|
||||||
]
|
d.get("ordered_qty"),
|
||||||
)
|
d.get("planned_qty"),
|
||||||
|
d.get("reserved_qty_for_production"),
|
||||||
|
d.get("safety_stock"),
|
||||||
|
d.get("quantity"),
|
||||||
|
]
|
||||||
|
|
||||||
|
duplicate_item_wh_list[key] = rm_data
|
||||||
|
item_list.append(rm_data)
|
||||||
|
|
||||||
if not doc.get("for_warehouse"):
|
if not doc.get("for_warehouse"):
|
||||||
row = {"item_code": d.get("item_code")}
|
row = {"item_code": d.get("item_code")}
|
||||||
|
|||||||
@@ -131,11 +131,11 @@ def get_chart_data(periodic_data, columns):
|
|||||||
pending.append(periodic_data.get("Pending").get(d))
|
pending.append(periodic_data.get("Pending").get(d))
|
||||||
completed.append(periodic_data.get("Completed").get(d))
|
completed.append(periodic_data.get("Completed").get(d))
|
||||||
|
|
||||||
datasets.append({"name": "All Work Orders", "values": all_data})
|
datasets.append({"name": _("All Work Orders"), "values": all_data})
|
||||||
datasets.append({"name": "Not Started", "values": not_start})
|
datasets.append({"name": _("Not Started"), "values": not_start})
|
||||||
datasets.append({"name": "Overdue", "values": overdue})
|
datasets.append({"name": _("Overdue"), "values": overdue})
|
||||||
datasets.append({"name": "Pending", "values": pending})
|
datasets.append({"name": _("Pending"), "values": pending})
|
||||||
datasets.append({"name": "Completed", "values": completed})
|
datasets.append({"name": _("Completed"), "values": completed})
|
||||||
|
|
||||||
chart = {"data": {"labels": labels, "datasets": datasets}}
|
chart = {"data": {"labels": labels, "datasets": datasets}}
|
||||||
chart["type"] = "line"
|
chart["type"] = "line"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ def get_data(filters):
|
|||||||
|
|
||||||
|
|
||||||
def get_chart_data(periodic_data, columns):
|
def get_chart_data(periodic_data, columns):
|
||||||
labels = ["Rejected", "Accepted"]
|
labels = [_("Rejected"), _("Accepted")]
|
||||||
|
|
||||||
status_wise_data = {"Accepted": 0, "Rejected": 0}
|
status_wise_data = {"Accepted": 0, "Rejected": 0}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ def get_chart_data(periodic_data, columns):
|
|||||||
|
|
||||||
datasets.append(
|
datasets.append(
|
||||||
{
|
{
|
||||||
"name": "Qty Wise Chart",
|
"name": _("Qty Wise Chart"),
|
||||||
"values": [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")],
|
"values": [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -384,3 +384,4 @@ erpnext.patches.v15_0.update_task_assignee_email_field_in_asset_maintenance_log
|
|||||||
erpnext.patches.v15_0.update_sub_voucher_type_in_gl_entries
|
erpnext.patches.v15_0.update_sub_voucher_type_in_gl_entries
|
||||||
erpnext.patches.v14_0.update_stock_uom_in_work_order_item
|
erpnext.patches.v14_0.update_stock_uom_in_work_order_item
|
||||||
erpnext.patches.v15_0.set_is_exchange_gain_loss_in_payment_entry_deductions
|
erpnext.patches.v15_0.set_is_exchange_gain_loss_in_payment_entry_deductions
|
||||||
|
erpnext.patches.v15_0.enable_allow_existing_serial_no
|
||||||
|
|||||||
6
erpnext/patches/v15_0/enable_allow_existing_serial_no.py
Normal file
6
erpnext/patches/v15_0/enable_allow_existing_serial_no.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if frappe.get_all("Company", filters={"country": "India"}, limit=1):
|
||||||
|
frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1)
|
||||||
@@ -58,10 +58,10 @@ frappe.ui.form.on("Timesheet", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus < 1) {
|
if (frm.doc.docstatus < 1) {
|
||||||
let button = "Start Timer";
|
let button = __("Start Timer");
|
||||||
$.each(frm.doc.time_logs || [], function (i, row) {
|
$.each(frm.doc.time_logs || [], function (i, row) {
|
||||||
if (row.from_time <= frappe.datetime.now_datetime() && !row.completed) {
|
if (row.from_time <= frappe.datetime.now_datetime() && !row.completed) {
|
||||||
button = "Resume Timer";
|
button = __("Resume Timer");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ def get_chart_data(data):
|
|||||||
charts = {
|
charts = {
|
||||||
"data": {
|
"data": {
|
||||||
"labels": [_("On Track"), _("Delayed")],
|
"labels": [_("On Track"), _("Delayed")],
|
||||||
"datasets": [{"name": "Delayed", "values": [on_track, delay]}],
|
"datasets": [{"name": _("Delayed"), "values": [on_track, delay]}],
|
||||||
},
|
},
|
||||||
"type": "percentage",
|
"type": "percentage",
|
||||||
"colors": ["#84D5BA", "#CB4B5F"],
|
"colors": ["#84D5BA", "#CB4B5F"],
|
||||||
|
|||||||
@@ -455,6 +455,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"fieldname": "projected_qty",
|
"fieldname": "projected_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Projected Qty",
|
"label": "Projected Qty",
|
||||||
@@ -690,7 +691,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-11-24 15:18:43.952844",
|
"modified": "2024-12-12 13:49:17.765883",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation Item",
|
"name": "Quotation Item",
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
target: frm,
|
target: frm,
|
||||||
setters: [
|
setters: [
|
||||||
{
|
{
|
||||||
label: "Supplier",
|
label: __("Supplier"),
|
||||||
fieldname: "supplier",
|
fieldname: "supplier",
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Supplier",
|
options: "Supplier",
|
||||||
@@ -783,7 +783,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
|||||||
target: me.frm,
|
target: me.frm,
|
||||||
setters: [
|
setters: [
|
||||||
{
|
{
|
||||||
label: "Customer",
|
label: __("Customer"),
|
||||||
fieldname: "party_name",
|
fieldname: "party_name",
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Customer",
|
options: "Customer",
|
||||||
@@ -838,7 +838,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
|||||||
} else {
|
} else {
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
label: "Items",
|
label: __("Items"),
|
||||||
fieldtype: "Table",
|
fieldtype: "Table",
|
||||||
fieldname: "items",
|
fieldname: "items",
|
||||||
description: __("Select BOM and Qty for Production"),
|
description: __("Select BOM and Qty for Production"),
|
||||||
@@ -1193,7 +1193,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
|||||||
{
|
{
|
||||||
fieldname: "items_for_po",
|
fieldname: "items_for_po",
|
||||||
fieldtype: "Table",
|
fieldtype: "Table",
|
||||||
label: "Select Items",
|
label: __("Select Items"),
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldtype: "Data",
|
fieldtype: "Data",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
fieldname: "mode_of_payment",
|
fieldname: "mode_of_payment",
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
label: "Mode of Payment",
|
label: __("Mode of Payment"),
|
||||||
options: "Mode of Payment",
|
options: "Mode of Payment",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
},
|
},
|
||||||
@@ -38,7 +38,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
fieldname: "opening_amount",
|
fieldname: "opening_amount",
|
||||||
fieldtype: "Currency",
|
fieldtype: "Currency",
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
label: "Opening Amount",
|
label: __("Opening Amount"),
|
||||||
options: "company:company_currency",
|
options: "company:company_currency",
|
||||||
change: function () {
|
change: function () {
|
||||||
dialog.fields_dict.balance_details.df.data.some((d) => {
|
dialog.fields_dict.balance_details.df.data.some((d) => {
|
||||||
@@ -87,7 +87,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
{
|
{
|
||||||
fieldname: "balance_details",
|
fieldname: "balance_details",
|
||||||
fieldtype: "Table",
|
fieldtype: "Table",
|
||||||
label: "Opening Balance Details",
|
label: __("Opening Balance Details"),
|
||||||
cannot_add_rows: false,
|
cannot_add_rows: false,
|
||||||
in_place_edit: true,
|
in_place_edit: true,
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
|
|||||||
@@ -966,13 +966,15 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
|
|
||||||
if (!res.length) {
|
if (!res.length) {
|
||||||
transaction_container.html(
|
transaction_container.html(
|
||||||
`<div class="no-transactions-placeholder">No recent transactions found</div>`
|
`<div class="no-transactions-placeholder">${__("No recent transactions found")}</div>`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elapsed_time = moment(res[0].posting_date + " " + res[0].posting_time).fromNow();
|
const elapsed_time = moment(res[0].posting_date + " " + res[0].posting_time).fromNow();
|
||||||
this.$customer_section.find(".customer-desc").html(`Last transacted ${elapsed_time}`);
|
this.$customer_section
|
||||||
|
.find(".customer-desc")
|
||||||
|
.html(`${__("Last transacted")} ${__(elapsed_time)}`);
|
||||||
|
|
||||||
res.forEach((invoice) => {
|
res.forEach((invoice) => {
|
||||||
const posting_datetime = moment(invoice.posting_date + " " + invoice.posting_time).format(
|
const posting_datetime = moment(invoice.posting_date + " " + invoice.posting_time).format(
|
||||||
@@ -997,7 +999,7 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
</div>
|
</div>
|
||||||
<div class="invoice-status">
|
<div class="invoice-status">
|
||||||
<span class="indicator-pill whitespace-nowrap ${indicator_color[invoice.status]}">
|
<span class="indicator-pill whitespace-nowrap ${indicator_color[invoice.status]}">
|
||||||
<span>${invoice.status}</span>
|
<span>${__(invoice.status)}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
|
|
||||||
init_email_print_dialog() {
|
init_email_print_dialog() {
|
||||||
const email_dialog = new frappe.ui.Dialog({
|
const email_dialog = new frappe.ui.Dialog({
|
||||||
title: "Email Receipt",
|
title: __("Email Receipt"),
|
||||||
fields: [
|
fields: [
|
||||||
{ fieldname: "email_id", fieldtype: "Data", options: "Email", label: "Email ID", reqd: 1 },
|
{ fieldname: "email_id", fieldtype: "Data", options: "Email", label: "Email ID", reqd: 1 },
|
||||||
{ fieldname: "content", fieldtype: "Small Text", label: "Message (if any)" },
|
{ fieldname: "content", fieldtype: "Small Text", label: "Message (if any)" },
|
||||||
@@ -59,7 +59,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
this.email_dialog = email_dialog;
|
this.email_dialog = email_dialog;
|
||||||
|
|
||||||
const print_dialog = new frappe.ui.Dialog({
|
const print_dialog = new frappe.ui.Dialog({
|
||||||
title: "Print Receipt",
|
title: __("Print Receipt"),
|
||||||
fields: [{ fieldname: "print", fieldtype: "Data", label: "Print Preview" }],
|
fields: [{ fieldname: "print", fieldtype: "Data", label: "Print Preview" }],
|
||||||
primary_action: () => {
|
primary_action: () => {
|
||||||
this.print_receipt();
|
this.print_receipt();
|
||||||
@@ -112,7 +112,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
get_discount_html(doc) {
|
get_discount_html(doc) {
|
||||||
if (doc.discount_amount) {
|
if (doc.discount_amount) {
|
||||||
return `<div class="summary-row-wrapper">
|
return `<div class="summary-row-wrapper">
|
||||||
<div>Discount (${doc.additional_discount_percentage} %)</div>
|
<div>${__("Discount")} (${doc.additional_discount_percentage} %)</div>
|
||||||
<div>${format_currency(doc.discount_amount, doc.currency)}</div>
|
<div>${format_currency(doc.discount_amount, doc.currency)}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -589,7 +589,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
const remaining = grand_total - doc.paid_amount;
|
const remaining = grand_total - doc.paid_amount;
|
||||||
const change = doc.change_amount || remaining <= 0 ? -1 * remaining : undefined;
|
const change = doc.change_amount || remaining <= 0 ? -1 * remaining : undefined;
|
||||||
const currency = doc.currency;
|
const currency = doc.currency;
|
||||||
const label = change ? __("Change") : __("To Be Paid");
|
const label = __("Change Amount");
|
||||||
|
|
||||||
this.$totals.html(
|
this.$totals.html(
|
||||||
`<div class="col">
|
`<div class="col">
|
||||||
|
|||||||
@@ -270,11 +270,11 @@ def prepare_chart(s_orders):
|
|||||||
"labels": [term.payment_term for term in s_orders],
|
"labels": [term.payment_term for term in s_orders],
|
||||||
"datasets": [
|
"datasets": [
|
||||||
{
|
{
|
||||||
"name": "Payment Amount",
|
"name": _("Payment Amount"),
|
||||||
"values": [x.base_payment_amount for x in s_orders],
|
"values": [x.base_payment_amount for x in s_orders],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Paid Amount",
|
"name": _("Paid Amount"),
|
||||||
"values": [x.paid_amount for x in s_orders],
|
"values": [x.paid_amount for x in s_orders],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ def prepare_data(data, so_elapsed_time, filters):
|
|||||||
|
|
||||||
|
|
||||||
def prepare_chart_data(pending, completed):
|
def prepare_chart_data(pending, completed):
|
||||||
labels = ["Amount to Bill", "Billed Amount"]
|
labels = [_("Amount to Bill"), _("Billed Amount")]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"data": {"labels": labels, "datasets": [{"values": [pending, completed]}]},
|
"data": {"labels": labels, "datasets": [{"values": [pending, completed]}]},
|
||||||
|
|||||||
@@ -1046,7 +1046,7 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
|
|||||||
automatically_fetch_payment_terms = cint(
|
automatically_fetch_payment_terms = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
|
frappe.db.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
|
||||||
)
|
)
|
||||||
if automatically_fetch_payment_terms:
|
if automatically_fetch_payment_terms and not doc.is_return:
|
||||||
doc.set_payment_schedule()
|
doc.set_payment_schedule()
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|||||||
@@ -214,13 +214,10 @@ class InventoryDimension(Document):
|
|||||||
dimension_fields = []
|
dimension_fields = []
|
||||||
if self.apply_to_all_doctypes:
|
if self.apply_to_all_doctypes:
|
||||||
for doctype in get_inventory_documents():
|
for doctype in get_inventory_documents():
|
||||||
if field_exists(doctype[0], self.source_fieldname):
|
|
||||||
continue
|
|
||||||
|
|
||||||
dimension_fields = self.get_dimension_fields(doctype[0])
|
dimension_fields = self.get_dimension_fields(doctype[0])
|
||||||
self.add_transfer_field(doctype[0], dimension_fields)
|
self.add_transfer_field(doctype[0], dimension_fields)
|
||||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
custom_fields.setdefault(doctype[0], dimension_fields)
|
||||||
elif not field_exists(self.document_type, self.source_fieldname):
|
else:
|
||||||
dimension_fields = self.get_dimension_fields()
|
dimension_fields = self.get_dimension_fields()
|
||||||
|
|
||||||
self.add_transfer_field(self.document_type, dimension_fields)
|
self.add_transfer_field(self.document_type, dimension_fields)
|
||||||
@@ -239,8 +236,17 @@ class InventoryDimension(Document):
|
|||||||
dimension_field["fieldname"] = self.target_fieldname
|
dimension_field["fieldname"] = self.target_fieldname
|
||||||
custom_fields["Stock Ledger Entry"] = dimension_field
|
custom_fields["Stock Ledger Entry"] = dimension_field
|
||||||
|
|
||||||
|
filter_custom_fields = {}
|
||||||
if custom_fields:
|
if custom_fields:
|
||||||
create_custom_fields(custom_fields)
|
for doctype, fields in custom_fields.items():
|
||||||
|
if isinstance(fields, dict):
|
||||||
|
fields = [fields]
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
if not field_exists(doctype, field["fieldname"]):
|
||||||
|
filter_custom_fields.setdefault(doctype, []).append(field)
|
||||||
|
|
||||||
|
create_custom_fields(filter_custom_fields)
|
||||||
|
|
||||||
def add_transfer_field(self, doctype, dimension_fields):
|
def add_transfer_field(self, doctype, dimension_fields):
|
||||||
if doctype not in [
|
if doctype not in [
|
||||||
|
|||||||
@@ -107,6 +107,14 @@ frappe.ui.form.on("Material Request", {
|
|||||||
|
|
||||||
if (flt(frm.doc.per_received, precision) < 100) {
|
if (flt(frm.doc.per_received, precision) < 100) {
|
||||||
frm.add_custom_button(__("Stop"), () => frm.events.update_status(frm, "Stopped"));
|
frm.add_custom_button(__("Stop"), () => frm.events.update_status(frm, "Stopped"));
|
||||||
|
|
||||||
|
if (frm.doc.material_request_type === "Purchase") {
|
||||||
|
frm.add_custom_button(
|
||||||
|
__("Purchase Order"),
|
||||||
|
() => frm.events.make_purchase_order(frm),
|
||||||
|
__("Create")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flt(frm.doc.per_ordered, precision) < 100) {
|
if (flt(frm.doc.per_ordered, precision) < 100) {
|
||||||
@@ -149,14 +157,6 @@ frappe.ui.form.on("Material Request", {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.material_request_type === "Purchase") {
|
|
||||||
frm.add_custom_button(
|
|
||||||
__("Purchase Order"),
|
|
||||||
() => frm.events.make_purchase_order(frm),
|
|
||||||
__("Create")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frm.doc.material_request_type === "Purchase") {
|
if (frm.doc.material_request_type === "Purchase") {
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Request for Quotation"),
|
__("Request for Quotation"),
|
||||||
@@ -259,18 +259,21 @@ frappe.ui.form.on("Material Request", {
|
|||||||
},
|
},
|
||||||
callback: function (r) {
|
callback: function (r) {
|
||||||
const d = item;
|
const d = item;
|
||||||
const allow_to_change_fields = [
|
let allow_to_change_fields = [
|
||||||
"actual_qty",
|
"actual_qty",
|
||||||
"projected_qty",
|
"projected_qty",
|
||||||
"min_order_qty",
|
"min_order_qty",
|
||||||
"item_name",
|
"item_name",
|
||||||
"description",
|
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"uom",
|
"uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"stock_qty",
|
"stock_qty",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (overwrite_warehouse) {
|
||||||
|
allow_to_change_fields.push("description");
|
||||||
|
}
|
||||||
|
|
||||||
if (!r.exc) {
|
if (!r.exc) {
|
||||||
$.each(r.message, function (key, value) {
|
$.each(r.message, function (key, value) {
|
||||||
if (!d[key] || allow_to_change_fields.includes(key)) {
|
if (!d[key] || allow_to_change_fields.includes(key)) {
|
||||||
|
|||||||
@@ -378,7 +378,9 @@ def set_missing_values(source, target_doc):
|
|||||||
|
|
||||||
def update_item(obj, target, source_parent):
|
def update_item(obj, target, source_parent):
|
||||||
target.conversion_factor = obj.conversion_factor
|
target.conversion_factor = obj.conversion_factor
|
||||||
target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty)) / target.conversion_factor
|
|
||||||
|
qty = obj.received_qty or obj.ordered_qty
|
||||||
|
target.qty = flt(flt(obj.stock_qty) - flt(qty)) / target.conversion_factor
|
||||||
target.stock_qty = target.qty * target.conversion_factor
|
target.stock_qty = target.qty * target.conversion_factor
|
||||||
if getdate(target.schedule_date) < getdate(nowdate()):
|
if getdate(target.schedule_date) < getdate(nowdate()):
|
||||||
target.schedule_date = None
|
target.schedule_date = None
|
||||||
@@ -430,7 +432,9 @@ def make_purchase_order(source_name, target_doc=None, args=None):
|
|||||||
filtered_items = args.get("filtered_children", [])
|
filtered_items = args.get("filtered_children", [])
|
||||||
child_filter = d.name in filtered_items if filtered_items else True
|
child_filter = d.name in filtered_items if filtered_items else True
|
||||||
|
|
||||||
return d.ordered_qty < d.stock_qty and child_filter
|
qty = d.received_qty or d.ordered_qty
|
||||||
|
|
||||||
|
return qty < d.stock_qty and child_filter
|
||||||
|
|
||||||
doclist = get_mapped_doc(
|
doclist = get_mapped_doc(
|
||||||
"Material Request",
|
"Material Request",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
frappe.listview_settings["Pick List"] = {
|
frappe.listview_settings["Pick List"] = {
|
||||||
get_indicator: function (doc) {
|
get_indicator: function (doc) {
|
||||||
const status_colors = {
|
const status_colors = {
|
||||||
Draft: "grey",
|
Draft: "red",
|
||||||
Open: "orange",
|
Open: "orange",
|
||||||
Completed: "green",
|
Completed: "green",
|
||||||
Cancelled: "red",
|
Cancelled: "red",
|
||||||
|
|||||||
@@ -3948,6 +3948,105 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
self.assertEqual(return_pr.per_billed, 100)
|
self.assertEqual(return_pr.per_billed, 100)
|
||||||
self.assertEqual(return_pr.status, "Completed")
|
self.assertEqual(return_pr.status, "Completed")
|
||||||
|
|
||||||
|
def test_do_not_allow_to_inward_same_serial_no_multiple_times(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 0)
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Do Not Allow INWD Item 123", {"has_serial_no": 1, "serial_no_series": "SN-TDAISN-.#####"}
|
||||||
|
).name
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code=item_code, qty=1, rate=100, use_serial_batch_fields=1)
|
||||||
|
serial_no = get_serial_nos_from_bundle(pr.items[0].serial_and_batch_bundle)[0]
|
||||||
|
|
||||||
|
status = frappe.db.get_value("Serial No", serial_no, "status")
|
||||||
|
self.assertTrue(status == "Active")
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
source=pr.items[0].warehouse,
|
||||||
|
qty=1,
|
||||||
|
serial_no=serial_no,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
status = frappe.db.get_value("Serial No", serial_no, "status")
|
||||||
|
self.assertFalse(status == "Active")
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item_code, qty=1, rate=100, use_serial_batch_fields=1, do_not_submit=1
|
||||||
|
)
|
||||||
|
pr.items[0].serial_no = serial_no
|
||||||
|
pr.save()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.exceptions.ValidationError, pr.submit)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1)
|
||||||
|
|
||||||
|
def test_seral_no_return_validation(self):
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||||
|
make_purchase_return,
|
||||||
|
)
|
||||||
|
|
||||||
|
sn_item_code = make_item(
|
||||||
|
"Test Serial No for Validation", {"has_serial_no": 1, "serial_no_series": "SN-TSNFVAL-.#####"}
|
||||||
|
).name
|
||||||
|
|
||||||
|
pr1 = make_purchase_receipt(item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1)
|
||||||
|
pr1_serial_nos = get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
serial_no_pr = make_purchase_receipt(
|
||||||
|
item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1
|
||||||
|
)
|
||||||
|
serial_no_pr_serial_nos = get_serial_nos_from_bundle(serial_no_pr.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
sn_return = make_purchase_return(serial_no_pr.name)
|
||||||
|
sn_return.items[0].qty = -1
|
||||||
|
sn_return.items[0].received_qty = -1
|
||||||
|
sn_return.items[0].serial_no = pr1_serial_nos[0]
|
||||||
|
sn_return.save()
|
||||||
|
self.assertRaises(frappe.ValidationError, sn_return.submit)
|
||||||
|
|
||||||
|
sn_return = make_purchase_return(serial_no_pr.name)
|
||||||
|
sn_return.items[0].qty = -1
|
||||||
|
sn_return.items[0].received_qty = -1
|
||||||
|
sn_return.items[0].serial_no = serial_no_pr_serial_nos[0]
|
||||||
|
sn_return.save()
|
||||||
|
sn_return.submit()
|
||||||
|
|
||||||
|
def test_batch_no_return_validation(self):
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||||
|
make_purchase_return,
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_item_code = make_item(
|
||||||
|
"Test Batch No for Validation",
|
||||||
|
{"has_batch_no": 1, "batch_number_series": "BT-TSNFVAL-.#####", "create_new_batch": 1},
|
||||||
|
).name
|
||||||
|
|
||||||
|
pr1 = make_purchase_receipt(item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1)
|
||||||
|
batch_no = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
batch_no_pr = make_purchase_receipt(
|
||||||
|
item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1
|
||||||
|
)
|
||||||
|
original_batch_no = get_batch_from_bundle(batch_no_pr.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
batch_return = make_purchase_return(batch_no_pr.name)
|
||||||
|
batch_return.items[0].qty = -1
|
||||||
|
batch_return.items[0].received_qty = -1
|
||||||
|
batch_return.items[0].batch_no = batch_no
|
||||||
|
batch_return.save()
|
||||||
|
self.assertRaises(frappe.ValidationError, batch_return.submit)
|
||||||
|
|
||||||
|
batch_return = make_purchase_return(batch_no_pr.name)
|
||||||
|
batch_return.items[0].qty = -1
|
||||||
|
batch_return.items[0].received_qty = -1
|
||||||
|
batch_return.items[0].batch_no = original_batch_no
|
||||||
|
batch_return.save()
|
||||||
|
batch_return.submit()
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ class SerialandBatchBundle(Document):
|
|||||||
self.validate_serial_and_batch_no()
|
self.validate_serial_and_batch_no()
|
||||||
self.validate_duplicate_serial_and_batch_no()
|
self.validate_duplicate_serial_and_batch_no()
|
||||||
self.validate_voucher_no()
|
self.validate_voucher_no()
|
||||||
|
|
||||||
|
if self.docstatus == 0:
|
||||||
|
self.allow_existing_serial_nos()
|
||||||
|
|
||||||
if self.type_of_transaction == "Maintenance":
|
if self.type_of_transaction == "Maintenance":
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -102,6 +106,42 @@ class SerialandBatchBundle(Document):
|
|||||||
self.set_incoming_rate()
|
self.set_incoming_rate()
|
||||||
self.calculate_qty_and_amount()
|
self.calculate_qty_and_amount()
|
||||||
|
|
||||||
|
def allow_existing_serial_nos(self):
|
||||||
|
if self.type_of_transaction == "Outward" or not self.has_serial_no:
|
||||||
|
return
|
||||||
|
|
||||||
|
if frappe.db.get_single_value("Stock Settings", "allow_existing_serial_no"):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.voucher_type not in ["Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.voucher_type == "Stock Entry" and frappe.get_cached_value(
|
||||||
|
"Stock Entry", self.voucher_no, "purpose"
|
||||||
|
) in ["Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
filters={"serial_no": ("in", serial_nos), "docstatus": 1, "qty": ("<", 0)},
|
||||||
|
fields=["serial_no", "parent"],
|
||||||
|
)
|
||||||
|
|
||||||
|
note = "<br><br> <b>Note</b>:<br>"
|
||||||
|
for row in data:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"You can't process the serial number {0} as it has already been used in the SABB {1}. {2} if you want to inward same serial number multiple times then enabled 'Allow existing Serial No to be Manufactured/Received again' in the {3}"
|
||||||
|
).format(
|
||||||
|
row.serial_no,
|
||||||
|
get_link_to_form("Serial and Batch Bundle", row.parent),
|
||||||
|
note,
|
||||||
|
get_link_to_form("Stock Settings", "Stock Settings"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def reset_serial_batch_bundle(self):
|
def reset_serial_batch_bundle(self):
|
||||||
if self.is_new() and self.amended_from:
|
if self.is_new() and self.amended_from:
|
||||||
for field in ["is_cancelled", "is_rejected"]:
|
for field in ["is_cancelled", "is_rejected"]:
|
||||||
@@ -136,7 +176,12 @@ class SerialandBatchBundle(Document):
|
|||||||
return
|
return
|
||||||
|
|
||||||
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||||
kwargs = {"item_code": self.item_code, "warehouse": self.warehouse}
|
kwargs = {
|
||||||
|
"item_code": self.item_code,
|
||||||
|
"warehouse": self.warehouse,
|
||||||
|
"check_serial_nos": True,
|
||||||
|
"serial_nos": serial_nos,
|
||||||
|
}
|
||||||
if self.voucher_type == "POS Invoice":
|
if self.voucher_type == "POS Invoice":
|
||||||
kwargs["ignore_voucher_nos"] = [self.voucher_no]
|
kwargs["ignore_voucher_nos"] = [self.voucher_no]
|
||||||
|
|
||||||
@@ -177,6 +222,7 @@ class SerialandBatchBundle(Document):
|
|||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
"posting_time": self.posting_time,
|
"posting_time": self.posting_time,
|
||||||
"serial_nos": serial_nos,
|
"serial_nos": serial_nos,
|
||||||
|
"check_serial_nos": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -206,8 +252,8 @@ class SerialandBatchBundle(Document):
|
|||||||
]:
|
]:
|
||||||
return
|
return
|
||||||
|
|
||||||
if return_aginst := self.get_return_aginst(parent=parent):
|
if return_against := self.get_return_against(parent=parent):
|
||||||
self.set_valuation_rate_for_return_entry(return_aginst, save)
|
self.set_valuation_rate_for_return_entry(return_against, save)
|
||||||
elif self.type_of_transaction == "Outward":
|
elif self.type_of_transaction == "Outward":
|
||||||
self.set_incoming_rate_for_outward_transaction(
|
self.set_incoming_rate_for_outward_transaction(
|
||||||
row, save, allow_negative_stock=allow_negative_stock
|
row, save, allow_negative_stock=allow_negative_stock
|
||||||
@@ -215,15 +261,18 @@ class SerialandBatchBundle(Document):
|
|||||||
else:
|
else:
|
||||||
self.set_incoming_rate_for_inward_transaction(row, save)
|
self.set_incoming_rate_for_inward_transaction(row, save)
|
||||||
|
|
||||||
def set_valuation_rate_for_return_entry(self, return_aginst, save=False):
|
def set_valuation_rate_for_return_entry(self, return_against, save=False):
|
||||||
if valuation_details := self.get_valuation_rate_for_return_entry(return_aginst):
|
if valuation_details := self.get_valuation_rate_for_return_entry(return_against):
|
||||||
for row in self.entries:
|
for row in self.entries:
|
||||||
|
if valuation_details:
|
||||||
|
self.validate_returned_serial_batch_no(return_against, row, valuation_details)
|
||||||
|
|
||||||
if row.serial_no:
|
if row.serial_no:
|
||||||
valuation_rate = valuation_details["serial_nos"].get(row.serial_no)
|
valuation_rate = valuation_details["serial_nos"].get(row.serial_no)
|
||||||
else:
|
else:
|
||||||
valuation_rate = valuation_details["batches"].get(row.batch_no)
|
valuation_rate = valuation_details["batches"].get(row.batch_no)
|
||||||
|
|
||||||
row.incoming_rate = valuation_rate
|
row.incoming_rate = flt(valuation_rate)
|
||||||
row.stock_value_difference = flt(row.qty) * flt(row.incoming_rate)
|
row.stock_value_difference = flt(row.qty) * flt(row.incoming_rate)
|
||||||
|
|
||||||
if save:
|
if save:
|
||||||
@@ -234,7 +283,22 @@ class SerialandBatchBundle(Document):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_valuation_rate_for_return_entry(self, return_aginst):
|
def validate_returned_serial_batch_no(self, return_against, row, original_inv_details):
|
||||||
|
if row.serial_no and row.serial_no not in original_inv_details["serial_nos"]:
|
||||||
|
self.throw_error_message(
|
||||||
|
_(
|
||||||
|
"Serial No {0} is not present in the {1} {2}, hence you can't return it against the {1} {2}"
|
||||||
|
).format(bold(row.serial_no), self.voucher_type, bold(return_against))
|
||||||
|
)
|
||||||
|
|
||||||
|
if row.batch_no and row.batch_no not in original_inv_details["batches"]:
|
||||||
|
self.throw_error_message(
|
||||||
|
_(
|
||||||
|
"Batch No {0} is not present in the original {1} {2}, hence you can't return it against the {1} {2}"
|
||||||
|
).format(bold(row.batch_no), self.voucher_type, bold(return_against))
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_valuation_rate_for_return_entry(self, return_against):
|
||||||
valuation_details = frappe._dict(
|
valuation_details = frappe._dict(
|
||||||
{
|
{
|
||||||
"serial_nos": defaultdict(float),
|
"serial_nos": defaultdict(float),
|
||||||
@@ -250,7 +314,7 @@ class SerialandBatchBundle(Document):
|
|||||||
"`tabSerial and Batch Entry`.`incoming_rate`",
|
"`tabSerial and Batch Entry`.`incoming_rate`",
|
||||||
],
|
],
|
||||||
filters=[
|
filters=[
|
||||||
["Serial and Batch Bundle", "voucher_no", "=", return_aginst],
|
["Serial and Batch Bundle", "voucher_no", "=", return_against],
|
||||||
["Serial and Batch Entry", "docstatus", "=", 1],
|
["Serial and Batch Entry", "docstatus", "=", 1],
|
||||||
["Serial and Batch Bundle", "is_cancelled", "=", 0],
|
["Serial and Batch Bundle", "is_cancelled", "=", 0],
|
||||||
["Serial and Batch Bundle", "item_code", "=", self.item_code],
|
["Serial and Batch Bundle", "item_code", "=", self.item_code],
|
||||||
@@ -384,8 +448,8 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
return sle
|
return sle
|
||||||
|
|
||||||
def get_return_aginst(self, parent=None):
|
def get_return_against(self, parent=None):
|
||||||
return_aginst = None
|
return_against = None
|
||||||
|
|
||||||
if parent and parent.get("is_return") and parent.get("return_against"):
|
if parent and parent.get("is_return") and parent.get("return_against"):
|
||||||
return parent.get("return_against")
|
return parent.get("return_against")
|
||||||
@@ -409,7 +473,7 @@ class SerialandBatchBundle(Document):
|
|||||||
if voucher_details and voucher_details.get("is_return") and voucher_details.get("return_against"):
|
if voucher_details and voucher_details.get("is_return") and voucher_details.get("return_against"):
|
||||||
return voucher_details.get("return_against")
|
return voucher_details.get("return_against")
|
||||||
|
|
||||||
return return_aginst
|
return return_against
|
||||||
|
|
||||||
def set_incoming_rate_for_inward_transaction(self, row=None, save=False):
|
def set_incoming_rate_for_inward_transaction(self, row=None, save=False):
|
||||||
valuation_field = "valuation_rate"
|
valuation_field = "valuation_rate"
|
||||||
@@ -1683,7 +1747,7 @@ def get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos):
|
|||||||
serial_nos = set()
|
serial_nos = set()
|
||||||
data = get_stock_ledgers_for_serial_nos(kwargs)
|
data = get_stock_ledgers_for_serial_nos(kwargs)
|
||||||
|
|
||||||
bundle_wise_serial_nos = get_bundle_wise_serial_nos(data)
|
bundle_wise_serial_nos = get_bundle_wise_serial_nos(data, kwargs)
|
||||||
for d in data:
|
for d in data:
|
||||||
if d.serial_and_batch_bundle:
|
if d.serial_and_batch_bundle:
|
||||||
if sns := bundle_wise_serial_nos.get(d.serial_and_batch_bundle):
|
if sns := bundle_wise_serial_nos.get(d.serial_and_batch_bundle):
|
||||||
@@ -1707,16 +1771,21 @@ def get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos):
|
|||||||
return serial_nos
|
return serial_nos
|
||||||
|
|
||||||
|
|
||||||
def get_bundle_wise_serial_nos(data):
|
def get_bundle_wise_serial_nos(data, kwargs):
|
||||||
bundle_wise_serial_nos = defaultdict(list)
|
bundle_wise_serial_nos = defaultdict(list)
|
||||||
bundles = [d.serial_and_batch_bundle for d in data if d.serial_and_batch_bundle]
|
bundles = [d.serial_and_batch_bundle for d in data if d.serial_and_batch_bundle]
|
||||||
if not bundles:
|
if not bundles:
|
||||||
return bundle_wise_serial_nos
|
return bundle_wise_serial_nos
|
||||||
|
|
||||||
|
filters = {"parent": ("in", bundles), "docstatus": 1, "serial_no": ("is", "set")}
|
||||||
|
|
||||||
|
if kwargs.get("check_serial_nos") and kwargs.get("serial_nos"):
|
||||||
|
filters["serial_no"] = ("in", kwargs.get("serial_nos"))
|
||||||
|
|
||||||
bundle_data = frappe.get_all(
|
bundle_data = frappe.get_all(
|
||||||
"Serial and Batch Entry",
|
"Serial and Batch Entry",
|
||||||
fields=["serial_no", "parent"],
|
fields=["serial_no", "parent"],
|
||||||
filters={"parent": ("in", bundles), "docstatus": 1, "serial_no": ("is", "set")},
|
filters=filters,
|
||||||
)
|
)
|
||||||
|
|
||||||
for d in bundle_data:
|
for d in bundle_data:
|
||||||
@@ -2277,6 +2346,8 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
|
|||||||
|
|
||||||
|
|
||||||
def get_stock_ledgers_for_serial_nos(kwargs):
|
def get_stock_ledgers_for_serial_nos(kwargs):
|
||||||
|
from erpnext.stock.utils import get_combine_datetime
|
||||||
|
|
||||||
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
@@ -2287,15 +2358,16 @@ def get_stock_ledgers_for_serial_nos(kwargs):
|
|||||||
stock_ledger_entry.serial_and_batch_bundle,
|
stock_ledger_entry.serial_and_batch_bundle,
|
||||||
)
|
)
|
||||||
.where(stock_ledger_entry.is_cancelled == 0)
|
.where(stock_ledger_entry.is_cancelled == 0)
|
||||||
|
.orderby(stock_ledger_entry.posting_datetime)
|
||||||
)
|
)
|
||||||
|
|
||||||
if kwargs.get("posting_date"):
|
if kwargs.get("posting_date"):
|
||||||
if kwargs.get("posting_time") is None:
|
if kwargs.get("posting_time") is None:
|
||||||
kwargs.posting_time = nowtime()
|
kwargs.posting_time = nowtime()
|
||||||
|
|
||||||
timestamp_condition = CombineDatetime(
|
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
|
||||||
stock_ledger_entry.posting_date, stock_ledger_entry.posting_time
|
kwargs.posting_date, kwargs.posting_time
|
||||||
) <= CombineDatetime(kwargs.posting_date, kwargs.posting_time)
|
)
|
||||||
|
|
||||||
query = query.where(timestamp_condition)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
|
|||||||
@@ -1645,6 +1645,46 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
mr.cancel()
|
mr.cancel()
|
||||||
mr.delete()
|
mr.delete()
|
||||||
|
|
||||||
|
def test_auto_reorder_level_with_lead_time_days(self):
|
||||||
|
from erpnext.stock.reorder_item import reorder_item
|
||||||
|
|
||||||
|
item_doc = make_item(
|
||||||
|
"Test Auto Reorder Item - 002",
|
||||||
|
properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1, "lead_time_days": 2},
|
||||||
|
uoms=[{"uom": "Nos", "conversion_factor": 5}],
|
||||||
|
)
|
||||||
|
|
||||||
|
if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
|
||||||
|
item_doc.append(
|
||||||
|
"reorder_levels",
|
||||||
|
{
|
||||||
|
"warehouse_reorder_level": 0,
|
||||||
|
"warehouse_reorder_qty": 10,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"material_request_type": "Purchase",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
item_doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
|
||||||
|
|
||||||
|
mr_list = reorder_item()
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
|
||||||
|
mrs = frappe.get_all(
|
||||||
|
"Material Request Item",
|
||||||
|
fields=["schedule_date"],
|
||||||
|
filters={"item_code": item_doc.name, "uom": "Nos"},
|
||||||
|
)
|
||||||
|
|
||||||
|
for mri in mrs:
|
||||||
|
self.assertEqual(getdate(mri.schedule_date), getdate(add_days(today(), 2)))
|
||||||
|
|
||||||
|
for mr in mr_list:
|
||||||
|
mr.cancel()
|
||||||
|
mr.delete()
|
||||||
|
|
||||||
def test_use_serial_and_batch_fields(self):
|
def test_use_serial_and_batch_fields(self):
|
||||||
item = make_item(
|
item = make_item(
|
||||||
"Test Use Serial and Batch Item SN Item",
|
"Test Use Serial and Batch Item SN Item",
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ frappe.ui.form.on("Stock Reconciliation", {
|
|||||||
posting_time: frm.doc.posting_time,
|
posting_time: frm.doc.posting_time,
|
||||||
batch_no: d.batch_no,
|
batch_no: d.batch_no,
|
||||||
row: d,
|
row: d,
|
||||||
|
company: frm.doc.company,
|
||||||
},
|
},
|
||||||
callback: function (r) {
|
callback: function (r) {
|
||||||
const row = frappe.model.get_doc(cdt, cdn);
|
const row = frappe.model.get_doc(cdt, cdn);
|
||||||
|
|||||||
@@ -166,6 +166,24 @@ class StockReconciliation(StockController):
|
|||||||
if not frappe.db.exists("Item", item.item_code):
|
if not frappe.db.exists("Item", item.item_code):
|
||||||
frappe.throw(_("Item {0} does not exist").format(item.item_code))
|
frappe.throw(_("Item {0} does not exist").format(item.item_code))
|
||||||
|
|
||||||
|
item_details = frappe.get_cached_value(
|
||||||
|
"Item", item.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if not (item_details.has_serial_no or item_details.has_batch_no):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (
|
||||||
|
not item.use_serial_batch_fields
|
||||||
|
and not item.reconcile_all_serial_batch
|
||||||
|
and not item.serial_and_batch_bundle
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row # {0}: Please add Serial and Batch Bundle for Item {1}").format(
|
||||||
|
item.idx, frappe.bold(item.item_code)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle:
|
if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle:
|
||||||
bundle = self.get_bundle_for_specific_serial_batch(item)
|
bundle = self.get_bundle_for_specific_serial_batch(item)
|
||||||
item.current_serial_and_batch_bundle = bundle.name
|
item.current_serial_and_batch_bundle = bundle.name
|
||||||
@@ -181,13 +199,6 @@ class StockReconciliation(StockController):
|
|||||||
if voucher_detail_no and voucher_detail_no != item.name:
|
if voucher_detail_no and voucher_detail_no != item.name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item_details = frappe.get_cached_value(
|
|
||||||
"Item", item.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
|
|
||||||
)
|
|
||||||
|
|
||||||
if not (item_details.has_serial_no or item_details.has_batch_no):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not item.current_serial_and_batch_bundle:
|
if not item.current_serial_and_batch_bundle:
|
||||||
serial_and_batch_bundle = frappe.get_doc(
|
serial_and_batch_bundle = frappe.get_doc(
|
||||||
{
|
{
|
||||||
@@ -400,6 +411,28 @@ class StockReconciliation(StockController):
|
|||||||
item.qty = bundle_doc.total_qty
|
item.qty = bundle_doc.total_qty
|
||||||
item.valuation_rate = bundle_doc.avg_rate
|
item.valuation_rate = bundle_doc.avg_rate
|
||||||
|
|
||||||
|
elif item.serial_and_batch_bundle and item.qty:
|
||||||
|
self.update_existing_serial_and_batch_bundle(item)
|
||||||
|
|
||||||
|
def update_existing_serial_and_batch_bundle(self, item):
|
||||||
|
batch_details = frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
fields=["batch_no", "qty", "name"],
|
||||||
|
filters={"parent": item.serial_and_batch_bundle, "batch_no": ("is", "set")},
|
||||||
|
)
|
||||||
|
|
||||||
|
if batch_details and len(batch_details) == 1:
|
||||||
|
batch = batch_details[0]
|
||||||
|
if abs(batch.qty) == abs(item.qty):
|
||||||
|
return
|
||||||
|
|
||||||
|
update_values = {
|
||||||
|
"qty": item.qty,
|
||||||
|
"stock_value_difference": flt(item.valuation_rate) * flt(item.qty),
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.db.set_value("Serial and Batch Entry", batch.name, update_values)
|
||||||
|
|
||||||
def remove_items_with_no_change(self):
|
def remove_items_with_no_change(self):
|
||||||
"""Remove items if qty or rate is not changed"""
|
"""Remove items if qty or rate is not changed"""
|
||||||
self.difference_amount = 0.0
|
self.difference_amount = 0.0
|
||||||
@@ -433,6 +466,7 @@ class StockReconciliation(StockController):
|
|||||||
batch_no=item.batch_no,
|
batch_no=item.batch_no,
|
||||||
inventory_dimensions_dict=inventory_dimensions_dict,
|
inventory_dimensions_dict=inventory_dimensions_dict,
|
||||||
row=item,
|
row=item,
|
||||||
|
company=self.company,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -941,6 +975,7 @@ class StockReconciliation(StockController):
|
|||||||
self.posting_date,
|
self.posting_date,
|
||||||
self.posting_time,
|
self.posting_time,
|
||||||
row=row,
|
row=row,
|
||||||
|
company=self.company,
|
||||||
)
|
)
|
||||||
|
|
||||||
current_qty = item_dict.get("qty")
|
current_qty = item_dict.get("qty")
|
||||||
@@ -1275,6 +1310,7 @@ def get_stock_balance_for(
|
|||||||
with_valuation_rate: bool = True,
|
with_valuation_rate: bool = True,
|
||||||
inventory_dimensions_dict=None,
|
inventory_dimensions_dict=None,
|
||||||
row=None,
|
row=None,
|
||||||
|
company=None,
|
||||||
):
|
):
|
||||||
frappe.has_permission("Stock Reconciliation", "write", throw=True)
|
frappe.has_permission("Stock Reconciliation", "write", throw=True)
|
||||||
|
|
||||||
@@ -1334,6 +1370,21 @@ def get_stock_balance_for(
|
|||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if row.use_serial_batch_fields and row.batch_no:
|
||||||
|
rate = get_incoming_rate(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": row.item_code,
|
||||||
|
"warehouse": row.warehouse,
|
||||||
|
"qty": row.qty * -1,
|
||||||
|
"batch_no": row.batch_no,
|
||||||
|
"company": company,
|
||||||
|
"posting_date": posting_date,
|
||||||
|
"posting_time": posting_time,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"qty": qty,
|
"qty": qty,
|
||||||
"rate": rate,
|
"rate": rate,
|
||||||
|
|||||||
@@ -49,12 +49,13 @@
|
|||||||
"do_not_use_batchwise_valuation",
|
"do_not_use_batchwise_valuation",
|
||||||
"auto_create_serial_and_batch_bundle_for_outward",
|
"auto_create_serial_and_batch_bundle_for_outward",
|
||||||
"pick_serial_and_batch_based_on",
|
"pick_serial_and_batch_based_on",
|
||||||
|
"naming_series_prefix",
|
||||||
"column_break_mhzc",
|
"column_break_mhzc",
|
||||||
"disable_serial_no_and_batch_selector",
|
"disable_serial_no_and_batch_selector",
|
||||||
"use_naming_series",
|
"use_naming_series",
|
||||||
"naming_series_prefix",
|
|
||||||
"use_serial_batch_fields",
|
"use_serial_batch_fields",
|
||||||
"do_not_update_serial_batch_on_creation_of_auto_bundle",
|
"do_not_update_serial_batch_on_creation_of_auto_bundle",
|
||||||
|
"allow_existing_serial_no",
|
||||||
"stock_planning_tab",
|
"stock_planning_tab",
|
||||||
"auto_material_request",
|
"auto_material_request",
|
||||||
"auto_indent",
|
"auto_indent",
|
||||||
@@ -460,6 +461,12 @@
|
|||||||
"fieldname": "over_picking_allowance",
|
"fieldname": "over_picking_allowance",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"label": "Over Picking Allowance"
|
"label": "Over Picking Allowance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "allow_existing_serial_no",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow existing Serial No to be Manufactured/Received again"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -467,7 +474,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-07-29 14:55:19.093508",
|
"modified": "2024-12-09 17:52:36.030456",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Settings",
|
"name": "Stock Settings",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class StockSettings(Document):
|
|||||||
|
|
||||||
action_if_quality_inspection_is_not_submitted: DF.Literal["Stop", "Warn"]
|
action_if_quality_inspection_is_not_submitted: DF.Literal["Stop", "Warn"]
|
||||||
action_if_quality_inspection_is_rejected: DF.Literal["Stop", "Warn"]
|
action_if_quality_inspection_is_rejected: DF.Literal["Stop", "Warn"]
|
||||||
|
allow_existing_serial_no: DF.Check
|
||||||
allow_from_dn: DF.Check
|
allow_from_dn: DF.Check
|
||||||
allow_from_pr: DF.Check
|
allow_from_pr: DF.Check
|
||||||
allow_internal_transfer_at_arms_length_price: DF.Check
|
allow_internal_transfer_at_arms_length_price: DF.Check
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ def _reorder_item():
|
|||||||
"description": d.description,
|
"description": d.description,
|
||||||
"stock_uom": d.stock_uom,
|
"stock_uom": d.stock_uom,
|
||||||
"purchase_uom": d.purchase_uom,
|
"purchase_uom": d.purchase_uom,
|
||||||
|
"lead_time_days": d.lead_time_days,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -129,6 +130,7 @@ def get_items_for_reorder() -> dict[str, list]:
|
|||||||
item_table.brand,
|
item_table.brand,
|
||||||
item_table.variant_of,
|
item_table.variant_of,
|
||||||
item_table.has_variants,
|
item_table.has_variants,
|
||||||
|
item_table.lead_time_days,
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(item_table.disabled == 0)
|
(item_table.disabled == 0)
|
||||||
|
|||||||
@@ -508,7 +508,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
|||||||
serial_nos = self.get_serial_nos()
|
serial_nos = self.get_serial_nos()
|
||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
incoming_rate = self.get_incoming_rate_from_bundle(serial_no)
|
incoming_rate = self.get_incoming_rate_from_bundle(serial_no)
|
||||||
if not incoming_rate:
|
if incoming_rate is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.stock_value_change += incoming_rate
|
self.stock_value_change += incoming_rate
|
||||||
@@ -553,7 +553,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
|||||||
query = query.where(timestamp_condition)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
incoming_rate = query.run()
|
incoming_rate = query.run()
|
||||||
return flt(incoming_rate[0][0]) if incoming_rate else 0.0
|
return flt(incoming_rate[0][0]) if incoming_rate else None
|
||||||
|
|
||||||
def get_serial_nos(self):
|
def get_serial_nos(self):
|
||||||
if self.sle.get("serial_nos"):
|
if self.sle.get("serial_nos"):
|
||||||
|
|||||||
@@ -294,7 +294,7 @@
|
|||||||
"fieldname": "total",
|
"fieldname": "total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total",
|
"label": "Total",
|
||||||
"options": "currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -353,6 +353,7 @@
|
|||||||
"fieldname": "total_additional_costs",
|
"fieldname": "total_additional_costs",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Additional Costs",
|
"label": "Total Additional Costs",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"print_hide_if_no_value": 1,
|
"print_hide_if_no_value": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -450,21 +451,21 @@
|
|||||||
"options": "Project"
|
"options": "Project"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "tab_other_info",
|
"fieldname": "tab_other_info",
|
||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Other Info"
|
"label": "Other Info"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "tab_connections",
|
"fieldname": "tab_connections",
|
||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Connections",
|
"label": "Connections",
|
||||||
"show_dashboard": 1
|
"show_dashboard": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-01-03 20:56:04.670380",
|
"modified": "2024-12-06 15:21:49.924146",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Order",
|
"name": "Subcontracting Order",
|
||||||
@@ -519,4 +520,4 @@
|
|||||||
"timeline_field": "supplier",
|
"timeline_field": "supplier",
|
||||||
"title_field": "supplier_name",
|
"title_field": "supplier_name",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
frappe.listview_settings["Subcontracting Order"] = {
|
frappe.listview_settings["Subcontracting Order"] = {
|
||||||
get_indicator: function (doc) {
|
get_indicator: function (doc) {
|
||||||
const status_colors = {
|
const status_colors = {
|
||||||
Draft: "grey",
|
Draft: "red",
|
||||||
Open: "orange",
|
Open: "orange",
|
||||||
"Partially Received": "yellow",
|
"Partially Received": "yellow",
|
||||||
Completed: "green",
|
Completed: "green",
|
||||||
|
|||||||
@@ -185,7 +185,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate",
|
"label": "Rate",
|
||||||
"options": "currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
"options": "currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -269,6 +269,7 @@
|
|||||||
"fieldname": "service_cost_per_qty",
|
"fieldname": "service_cost_per_qty",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Service Cost Per Qty",
|
"label": "Service Cost Per Qty",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -277,6 +278,7 @@
|
|||||||
"fieldname": "additional_cost_per_qty",
|
"fieldname": "additional_cost_per_qty",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Additional Cost Per Qty",
|
"label": "Additional Cost Per Qty",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -284,6 +286,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Raw Material Cost Per Qty",
|
"label": "Raw Material Cost Per Qty",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -384,7 +387,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-30 15:29:43.744618",
|
"modified": "2024-12-06 15:23:05.252346",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Order Item",
|
"name": "Subcontracting Order Item",
|
||||||
@@ -397,4 +400,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ class SubcontractingOrderItem(Document):
|
|||||||
include_exploded_items: DF.Check
|
include_exploded_items: DF.Check
|
||||||
item_code: DF.Link
|
item_code: DF.Link
|
||||||
item_name: DF.Data
|
item_name: DF.Data
|
||||||
|
job_card: DF.Link | None
|
||||||
manufacturer: DF.Link | None
|
manufacturer: DF.Link | None
|
||||||
manufacturer_part_no: DF.Data | None
|
manufacturer_part_no: DF.Data | None
|
||||||
|
material_request: DF.Link | None
|
||||||
|
material_request_item: DF.Data | None
|
||||||
page_break: DF.Check
|
page_break: DF.Check
|
||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
|
|||||||
@@ -336,7 +336,7 @@
|
|||||||
"fieldname": "total",
|
"fieldname": "total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total",
|
"label": "Total",
|
||||||
"options": "currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -618,6 +618,7 @@
|
|||||||
"fieldname": "total_additional_costs",
|
"fieldname": "total_additional_costs",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Additional Costs",
|
"label": "Total Additional Costs",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"print_hide_if_no_value": 1,
|
"print_hide_if_no_value": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -656,27 +657,27 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "tab_other_info",
|
"fieldname": "tab_other_info",
|
||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Other Info"
|
"label": "Other Info"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "order_status_section",
|
"fieldname": "order_status_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Order Status"
|
"label": "Order Status"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "tab_connections",
|
"fieldname": "tab_connections",
|
||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Connections",
|
"label": "Connections",
|
||||||
"show_dashboard": 1
|
"show_dashboard": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-05-28 15:02:13.517969",
|
"modified": "2024-12-06 15:24:38.384232",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt",
|
"name": "Subcontracting Receipt",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
frappe.listview_settings["Subcontracting Receipt"] = {
|
frappe.listview_settings["Subcontracting Receipt"] = {
|
||||||
get_indicator: function (doc) {
|
get_indicator: function (doc) {
|
||||||
const status_colors = {
|
const status_colors = {
|
||||||
Draft: "grey",
|
Draft: "red",
|
||||||
Return: "gray",
|
Return: "gray",
|
||||||
"Return Issued": "grey",
|
"Return Issued": "grey",
|
||||||
Completed: "green",
|
Completed: "green",
|
||||||
|
|||||||
@@ -207,7 +207,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate",
|
"label": "Rate",
|
||||||
"options": "currency",
|
"options": "Company:company:default_currency",
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
"options": "currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -231,6 +231,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Raw Material Cost Per Qty",
|
"label": "Raw Material Cost Per Qty",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -239,6 +240,7 @@
|
|||||||
"fieldname": "service_cost_per_qty",
|
"fieldname": "service_cost_per_qty",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Service Cost Per Qty",
|
"label": "Service Cost Per Qty",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -248,6 +250,7 @@
|
|||||||
"fieldname": "additional_cost_per_qty",
|
"fieldname": "additional_cost_per_qty",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Additional Cost Per Qty",
|
"label": "Additional Cost Per Qty",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -582,7 +585,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-29 15:42:43.425544",
|
"modified": "2024-12-06 15:23:58.680169",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt Item",
|
"name": "Subcontracting Receipt Item",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class SubcontractingReceiptItem(Document):
|
|||||||
is_scrap_item: DF.Check
|
is_scrap_item: DF.Check
|
||||||
item_code: DF.Link
|
item_code: DF.Link
|
||||||
item_name: DF.Data | None
|
item_name: DF.Data | None
|
||||||
|
job_card: DF.Link | None
|
||||||
manufacturer: DF.Link | None
|
manufacturer: DF.Link | None
|
||||||
manufacturer_part_no: DF.Data | None
|
manufacturer_part_no: DF.Data | None
|
||||||
page_break: DF.Check
|
page_break: DF.Check
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{% if doc.status == "Open" %}
|
{% if doc.status == "Open" %}
|
||||||
{{ doc.priority }}
|
{{ doc.priority }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ doc.status }}
|
{{ _(doc.status) }}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="indicator-pill {{ " red" if doc.status=="Open" else "darkgrey" }}">
|
<span class="indicator-pill {{ " red" if doc.status=="Open" else "darkgrey" }}">
|
||||||
{{ doc.status }}</span>
|
{{ _(doc.status) }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if doc["_assign"] %}
|
{% if doc["_assign"] %}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<span class="indicator-pill {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "gray") }} list-item-status">{{doc.status}}</span>
|
<span class="indicator-pill {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "gray") }} list-item-status">{{ _(doc.status) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<div class="small text-muted items-preview ellipsis ellipsis-width">
|
<div class="small text-muted items-preview ellipsis ellipsis-width">
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
<input type="text" class="form-control" name="Hours" readonly value= "{{ doc.hours }}">
|
<input type="text" class="form-control" name="Hours" readonly value= "{{ doc.hours }}">
|
||||||
|
|
||||||
<label for="status" class="control-label text-muted small">{{ __("Status") }}</label>
|
<label for="status" class="control-label text-muted small">{{ __("Status") }}</label>
|
||||||
<input type="text" class="form-control" name="status" readonly value= "{{ doc.status }}">
|
<input type="text" class="form-control" name="status" readonly value= "{{ _(doc.status) }}">
|
||||||
|
|
||||||
<label for="Note" class="control-label text-muted small">{{ __("Note") }}</label>
|
<label for="Note" class="control-label text-muted small">{{ __("Note") }}</label>
|
||||||
<textarea class="form-control" name="Hours" readonly> {{ doc.note }} </textarea>
|
<textarea class="form-control" name="Hours" readonly> {{ doc.note }} </textarea>
|
||||||
|
|||||||
Reference in New Issue
Block a user