mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-17 13:55:10 +00:00
Merge pull request #39246 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
@@ -19,7 +20,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-01 12:32:34.044911",
|
||||
"modified": "2024-01-03 11:13:02.669632",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Allowed To Transact With",
|
||||
@@ -28,5 +29,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -521,7 +521,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
|
||||
values.extend(warehouses)
|
||||
|
||||
if items:
|
||||
condition = " and `tab{child_doc}`.{apply_on} in ({items})".format(
|
||||
condition += " and `tab{child_doc}`.{apply_on} in ({items})".format(
|
||||
child_doc=child_doctype, apply_on=apply_on, items=",".join(["%s"] * len(items))
|
||||
)
|
||||
|
||||
|
||||
@@ -925,17 +925,6 @@ class PurchaseInvoice(BuyingController):
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of asset bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)
|
||||
)
|
||||
|
||||
if (
|
||||
self.auto_accounting_for_stock
|
||||
and self.is_opening == "No"
|
||||
@@ -975,17 +964,24 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount, item.precision("item_tax_amount")
|
||||
)
|
||||
|
||||
if item.is_fixed_asset and item.landed_cost_voucher_amount:
|
||||
self.update_gross_purchase_amount_for_linked_assets(item)
|
||||
|
||||
def update_gross_purchase_amount_for_linked_assets(self, item):
|
||||
assets = frappe.db.get_all(
|
||||
"Asset",
|
||||
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
||||
fields=["name", "asset_quantity"],
|
||||
)
|
||||
for asset in assets:
|
||||
purchase_amount = flt(item.valuation_rate) * asset.asset_quantity
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||
"Asset",
|
||||
asset.name,
|
||||
{
|
||||
"gross_purchase_amount": purchase_amount,
|
||||
"purchase_receipt_amount": purchase_amount,
|
||||
},
|
||||
)
|
||||
|
||||
def make_stock_adjustment_entry(
|
||||
|
||||
@@ -890,8 +890,8 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
frm.events.append_time_log(frm, timesheet, 1.0);
|
||||
}
|
||||
});
|
||||
frm.refresh_field("timesheets");
|
||||
frm.trigger("calculate_timesheet_totals");
|
||||
frm.refresh();
|
||||
},
|
||||
|
||||
async get_exchange_rate(frm, from_currency, to_currency) {
|
||||
|
||||
@@ -114,14 +114,12 @@ def _get_party_details(
|
||||
set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)
|
||||
)
|
||||
party = party_details[party_type.lower()]
|
||||
|
||||
if not ignore_permissions and not (
|
||||
frappe.has_permission(party_type, "read", party)
|
||||
or frappe.has_permission(party_type, "select", party)
|
||||
):
|
||||
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
|
||||
|
||||
party = frappe.get_doc(party_type, party)
|
||||
|
||||
if not ignore_permissions:
|
||||
ptype = "select" if frappe.only_has_select_perm(party_type) else "read"
|
||||
frappe.has_permission(party_type, ptype, party, throw=True)
|
||||
|
||||
currency = party.get("default_currency") or currency or get_company_currency(company)
|
||||
|
||||
party_address, shipping_address = set_address_details(
|
||||
|
||||
@@ -519,10 +519,16 @@ frappe.ui.form.on('Asset', {
|
||||
indicator: 'red'
|
||||
});
|
||||
}
|
||||
frm.set_value('gross_purchase_amount', item.base_net_rate + item.item_tax_amount);
|
||||
frm.set_value('purchase_receipt_amount', item.base_net_rate + item.item_tax_amount);
|
||||
item.asset_location && frm.set_value('location', item.asset_location);
|
||||
var is_grouped_asset = frappe.db.get_value('Item', item.item_code, 'is_grouped_asset');
|
||||
var asset_quantity = is_grouped_asset ? item.qty : 1;
|
||||
var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
|
||||
|
||||
frm.set_value('gross_purchase_amount', purchase_amount);
|
||||
frm.set_value('purchase_receipt_amount', purchase_amount);
|
||||
frm.set_value('asset_quantity', asset_quantity);
|
||||
frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
|
||||
if(item.asset_location) { frm.set_value('location', item.asset_location); }
|
||||
|
||||
},
|
||||
|
||||
set_depreciation_rate: function(frm, row) {
|
||||
|
||||
@@ -202,9 +202,9 @@
|
||||
"fieldname": "purchase_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Purchase Date",
|
||||
"mandatory_depends_on": "eval:!doc.is_existing_asset",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
|
||||
"reqd": 1
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "disposal_date",
|
||||
@@ -227,15 +227,15 @@
|
||||
"fieldname": "gross_purchase_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Gross Purchase Amount",
|
||||
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
||||
"reqd": 1
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "available_for_use_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Available-for-use Date",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -583,7 +583,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2023-12-21 16:46:20.732869",
|
||||
"modified": "2024-01-05 17:36:53.131512",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -247,7 +247,12 @@ class Asset(AccountsController):
|
||||
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
|
||||
|
||||
if is_cwip_accounting_enabled(self.asset_category):
|
||||
if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
|
||||
if (
|
||||
not self.is_existing_asset
|
||||
and not self.is_composite_asset
|
||||
and not self.purchase_receipt
|
||||
and not self.purchase_invoice
|
||||
):
|
||||
frappe.throw(
|
||||
_("Please create purchase receipt or purchase invoice for the item {0}").format(
|
||||
self.item_code
|
||||
@@ -260,7 +265,7 @@ class Asset(AccountsController):
|
||||
and not frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "update_stock")
|
||||
):
|
||||
frappe.throw(
|
||||
_("Update stock must be enable for the purchase invoice {0}").format(self.purchase_invoice)
|
||||
_("Update stock must be enabled for the purchase invoice {0}").format(self.purchase_invoice)
|
||||
)
|
||||
|
||||
if not self.calculate_depreciation:
|
||||
@@ -428,10 +433,7 @@ class Asset(AccountsController):
|
||||
n == 0
|
||||
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||
and not self.opening_accumulated_depreciation
|
||||
and get_updated_rate_of_depreciation_for_wdv_and_dd(
|
||||
self, value_after_depreciation, finance_book, False
|
||||
)
|
||||
== finance_book.rate_of_depreciation
|
||||
and not self.flags.wdv_it_act_applied
|
||||
):
|
||||
from_date = add_days(
|
||||
self.available_for_use_date, -1
|
||||
@@ -1378,26 +1380,16 @@ def get_depreciation_amount(
|
||||
asset, fb_row, schedule_idx, number_of_pending_depreciations
|
||||
)
|
||||
else:
|
||||
rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
|
||||
asset, depreciable_value, fb_row
|
||||
)
|
||||
return get_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
depreciable_value,
|
||||
rate_of_depreciation,
|
||||
fb_row.frequency_of_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_updated_rate_of_depreciation_for_wdv_and_dd(
|
||||
asset, depreciable_value, fb_row, show_msg=True
|
||||
):
|
||||
return fb_row.rate_of_depreciation
|
||||
|
||||
|
||||
def get_straight_line_or_manual_depr_amount(
|
||||
asset, row, schedule_idx, number_of_pending_depreciations
|
||||
):
|
||||
@@ -1532,30 +1524,53 @@ def get_asset_shift_factors_map():
|
||||
return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
depreciable_value,
|
||||
rate_of_depreciation,
|
||||
frequency_of_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
):
|
||||
if cint(frequency_of_depreciation) == 12:
|
||||
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
|
||||
return get_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
depreciable_value,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
|
||||
def get_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
depreciable_value,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
):
|
||||
if cint(fb_row.frequency_of_depreciation) == 12:
|
||||
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
|
||||
else:
|
||||
if has_wdv_or_dd_non_yearly_pro_rata:
|
||||
if schedule_idx == 0:
|
||||
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
|
||||
elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1:
|
||||
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
|
||||
elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1:
|
||||
return (
|
||||
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
|
||||
flt(depreciable_value)
|
||||
* flt(fb_row.frequency_of_depreciation)
|
||||
* (flt(fb_row.rate_of_depreciation) / 1200)
|
||||
)
|
||||
else:
|
||||
return prev_depreciation_amount
|
||||
else:
|
||||
if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0:
|
||||
if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0:
|
||||
return (
|
||||
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
|
||||
flt(depreciable_value)
|
||||
* flt(fb_row.frequency_of_depreciation)
|
||||
* (flt(fb_row.rate_of_depreciation) / 1200)
|
||||
)
|
||||
else:
|
||||
return prev_depreciation_amount
|
||||
|
||||
@@ -19,6 +19,7 @@ from frappe.utils import (
|
||||
from frappe.utils.data import get_link_to_form
|
||||
from frappe.utils.user import get_users_with_role
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_checks_for_pl_and_bs_accounts,
|
||||
)
|
||||
@@ -473,6 +474,13 @@ def depreciate_asset(asset, date):
|
||||
|
||||
make_depreciation_entry(asset.name, date)
|
||||
|
||||
cancel_depreciation_entries(asset, date)
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def cancel_depreciation_entries(asset, date):
|
||||
pass
|
||||
|
||||
|
||||
def reset_depreciation_schedule(asset, date):
|
||||
if not asset.calculate_depreciation:
|
||||
|
||||
@@ -67,12 +67,12 @@ class AssetCategory(Document):
|
||||
if selected_key_type not in expected_key_types:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: {} of {} should be {}. Please modify the account or select a different account."
|
||||
"Row #{0}: {1} of {2} should be {3}. Please update the {1} or select a different account."
|
||||
).format(
|
||||
d.idx,
|
||||
frappe.unscrub(key_to_match),
|
||||
frappe.bold(selected_account),
|
||||
frappe.bold(expected_key_types),
|
||||
frappe.bold(" or ".join(expected_key_types)),
|
||||
),
|
||||
title=_("Invalid Account"),
|
||||
)
|
||||
|
||||
@@ -90,7 +90,6 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
||||
"fieldname": "daily_prorata_based",
|
||||
"fieldtype": "Check",
|
||||
"label": "Depreciate based on daily pro-rata"
|
||||
@@ -106,7 +105,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-29 03:53:03.591098",
|
||||
"modified": "2023-12-29 08:49:39.876439",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-24 10:55:51.287327",
|
||||
"modified": "2024-01-05 15:26:02.320942",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
@@ -212,10 +212,45 @@
|
||||
"role": "Purchase Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Stock Manager",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Stock User",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Purchase User",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,11 +730,8 @@ class BuyingController(SubcontractingController):
|
||||
item_data = frappe.db.get_value(
|
||||
"Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1
|
||||
)
|
||||
|
||||
if is_grouped_asset:
|
||||
purchase_amount = flt(row.base_amount + row.item_tax_amount)
|
||||
else:
|
||||
purchase_amount = flt(row.base_rate + row.item_tax_amount)
|
||||
asset_quantity = row.qty if is_grouped_asset else 1
|
||||
purchase_amount = flt(row.valuation_rate) * asset_quantity
|
||||
|
||||
asset = frappe.get_doc(
|
||||
{
|
||||
@@ -750,7 +747,7 @@ class BuyingController(SubcontractingController):
|
||||
"calculate_depreciation": 1,
|
||||
"purchase_receipt_amount": purchase_amount,
|
||||
"gross_purchase_amount": purchase_amount,
|
||||
"asset_quantity": row.qty if is_grouped_asset else 1,
|
||||
"asset_quantity": asset_quantity,
|
||||
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
||||
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
||||
"cost_center": row.cost_center,
|
||||
|
||||
@@ -8,7 +8,7 @@ from frappe.model.meta import get_field_precision
|
||||
from frappe.utils import flt, format_datetime, get_datetime
|
||||
|
||||
import erpnext
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.utils import get_incoming_rate, get_valuation_method
|
||||
|
||||
|
||||
class StockOverReturnError(frappe.ValidationError):
|
||||
@@ -116,7 +116,12 @@ def validate_returned_items(doc):
|
||||
ref = valid_items.get(d.item_code, frappe._dict())
|
||||
validate_quantity(doc, d, ref, valid_items, already_returned_items)
|
||||
|
||||
if ref.rate and doc.doctype in ("Delivery Note", "Sales Invoice") and flt(d.rate) > ref.rate:
|
||||
if (
|
||||
ref.rate
|
||||
and flt(d.rate) > ref.rate
|
||||
and doc.doctype in ("Delivery Note", "Sales Invoice")
|
||||
and get_valuation_method(ref.item_code) != "Moving Average"
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(
|
||||
d.idx, doc.doctype, doc.return_against
|
||||
|
||||
@@ -422,6 +422,9 @@ class SellingController(StockController):
|
||||
|
||||
items = self.get("items") + (self.get("packed_items") or [])
|
||||
for d in items:
|
||||
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
|
||||
continue
|
||||
|
||||
if not self.get("return_against") or (
|
||||
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
|
||||
):
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from frappe import _
|
||||
|
||||
app_name = "erpnext"
|
||||
app_title = "ERPNext"
|
||||
app_publisher = "Frappe Technologies Pvt. Ltd."
|
||||
@@ -92,7 +90,7 @@ website_route_rules = [
|
||||
{
|
||||
"from_route": "/orders/<path:name>",
|
||||
"to_route": "order",
|
||||
"defaults": {"doctype": "Sales Order", "parents": [{"label": _("Orders"), "route": "orders"}]},
|
||||
"defaults": {"doctype": "Sales Order", "parents": [{"label": "Orders", "route": "orders"}]},
|
||||
},
|
||||
{"from_route": "/invoices", "to_route": "Sales Invoice"},
|
||||
{
|
||||
@@ -100,7 +98,7 @@ website_route_rules = [
|
||||
"to_route": "order",
|
||||
"defaults": {
|
||||
"doctype": "Sales Invoice",
|
||||
"parents": [{"label": _("Invoices"), "route": "invoices"}],
|
||||
"parents": [{"label": "Invoices", "route": "invoices"}],
|
||||
},
|
||||
},
|
||||
{"from_route": "/supplier-quotations", "to_route": "Supplier Quotation"},
|
||||
@@ -109,7 +107,7 @@ website_route_rules = [
|
||||
"to_route": "order",
|
||||
"defaults": {
|
||||
"doctype": "Supplier Quotation",
|
||||
"parents": [{"label": _("Supplier Quotation"), "route": "supplier-quotations"}],
|
||||
"parents": [{"label": "Supplier Quotation", "route": "supplier-quotations"}],
|
||||
},
|
||||
},
|
||||
{"from_route": "/purchase-orders", "to_route": "Purchase Order"},
|
||||
@@ -118,7 +116,7 @@ website_route_rules = [
|
||||
"to_route": "order",
|
||||
"defaults": {
|
||||
"doctype": "Purchase Order",
|
||||
"parents": [{"label": _("Purchase Order"), "route": "purchase-orders"}],
|
||||
"parents": [{"label": "Purchase Order", "route": "purchase-orders"}],
|
||||
},
|
||||
},
|
||||
{"from_route": "/purchase-invoices", "to_route": "Purchase Invoice"},
|
||||
@@ -127,7 +125,7 @@ website_route_rules = [
|
||||
"to_route": "order",
|
||||
"defaults": {
|
||||
"doctype": "Purchase Invoice",
|
||||
"parents": [{"label": _("Purchase Invoice"), "route": "purchase-invoices"}],
|
||||
"parents": [{"label": "Purchase Invoice", "route": "purchase-invoices"}],
|
||||
},
|
||||
},
|
||||
{"from_route": "/quotations", "to_route": "Quotation"},
|
||||
@@ -136,7 +134,7 @@ website_route_rules = [
|
||||
"to_route": "order",
|
||||
"defaults": {
|
||||
"doctype": "Quotation",
|
||||
"parents": [{"label": _("Quotations"), "route": "quotations"}],
|
||||
"parents": [{"label": "Quotations", "route": "quotations"}],
|
||||
},
|
||||
},
|
||||
{"from_route": "/shipments", "to_route": "Delivery Note"},
|
||||
@@ -145,7 +143,7 @@ website_route_rules = [
|
||||
"to_route": "order",
|
||||
"defaults": {
|
||||
"doctype": "Delivery Note",
|
||||
"parents": [{"label": _("Shipments"), "route": "shipments"}],
|
||||
"parents": [{"label": "Shipments", "route": "shipments"}],
|
||||
},
|
||||
},
|
||||
{"from_route": "/rfq", "to_route": "Request for Quotation"},
|
||||
@@ -154,14 +152,14 @@ website_route_rules = [
|
||||
"to_route": "rfq",
|
||||
"defaults": {
|
||||
"doctype": "Request for Quotation",
|
||||
"parents": [{"label": _("Request for Quotation"), "route": "rfq"}],
|
||||
"parents": [{"label": "Request for Quotation", "route": "rfq"}],
|
||||
},
|
||||
},
|
||||
{"from_route": "/addresses", "to_route": "Address"},
|
||||
{
|
||||
"from_route": "/addresses/<path:name>",
|
||||
"to_route": "addresses",
|
||||
"defaults": {"doctype": "Address", "parents": [{"label": _("Addresses"), "route": "addresses"}]},
|
||||
"defaults": {"doctype": "Address", "parents": [{"label": "Addresses", "route": "addresses"}]},
|
||||
},
|
||||
{"from_route": "/boms", "to_route": "BOM"},
|
||||
{"from_route": "/timesheets", "to_route": "Timesheet"},
|
||||
@@ -171,78 +169,78 @@ website_route_rules = [
|
||||
"to_route": "material_request_info",
|
||||
"defaults": {
|
||||
"doctype": "Material Request",
|
||||
"parents": [{"label": _("Material Request"), "route": "material-requests"}],
|
||||
"parents": [{"label": "Material Request", "route": "material-requests"}],
|
||||
},
|
||||
},
|
||||
{"from_route": "/project", "to_route": "Project"},
|
||||
]
|
||||
|
||||
standard_portal_menu_items = [
|
||||
{"title": _("Projects"), "route": "/project", "reference_doctype": "Project"},
|
||||
{"title": "Projects", "route": "/project", "reference_doctype": "Project"},
|
||||
{
|
||||
"title": _("Request for Quotations"),
|
||||
"title": "Request for Quotations",
|
||||
"route": "/rfq",
|
||||
"reference_doctype": "Request for Quotation",
|
||||
"role": "Supplier",
|
||||
},
|
||||
{
|
||||
"title": _("Supplier Quotation"),
|
||||
"title": "Supplier Quotation",
|
||||
"route": "/supplier-quotations",
|
||||
"reference_doctype": "Supplier Quotation",
|
||||
"role": "Supplier",
|
||||
},
|
||||
{
|
||||
"title": _("Purchase Orders"),
|
||||
"title": "Purchase Orders",
|
||||
"route": "/purchase-orders",
|
||||
"reference_doctype": "Purchase Order",
|
||||
"role": "Supplier",
|
||||
},
|
||||
{
|
||||
"title": _("Purchase Invoices"),
|
||||
"title": "Purchase Invoices",
|
||||
"route": "/purchase-invoices",
|
||||
"reference_doctype": "Purchase Invoice",
|
||||
"role": "Supplier",
|
||||
},
|
||||
{
|
||||
"title": _("Quotations"),
|
||||
"title": "Quotations",
|
||||
"route": "/quotations",
|
||||
"reference_doctype": "Quotation",
|
||||
"role": "Customer",
|
||||
},
|
||||
{
|
||||
"title": _("Orders"),
|
||||
"title": "Orders",
|
||||
"route": "/orders",
|
||||
"reference_doctype": "Sales Order",
|
||||
"role": "Customer",
|
||||
},
|
||||
{
|
||||
"title": _("Invoices"),
|
||||
"title": "Invoices",
|
||||
"route": "/invoices",
|
||||
"reference_doctype": "Sales Invoice",
|
||||
"role": "Customer",
|
||||
},
|
||||
{
|
||||
"title": _("Shipments"),
|
||||
"title": "Shipments",
|
||||
"route": "/shipments",
|
||||
"reference_doctype": "Delivery Note",
|
||||
"role": "Customer",
|
||||
},
|
||||
{"title": _("Issues"), "route": "/issues", "reference_doctype": "Issue", "role": "Customer"},
|
||||
{"title": _("Addresses"), "route": "/addresses", "reference_doctype": "Address"},
|
||||
{"title": "Issues", "route": "/issues", "reference_doctype": "Issue", "role": "Customer"},
|
||||
{"title": "Addresses", "route": "/addresses", "reference_doctype": "Address"},
|
||||
{
|
||||
"title": _("Timesheets"),
|
||||
"title": "Timesheets",
|
||||
"route": "/timesheets",
|
||||
"reference_doctype": "Timesheet",
|
||||
"role": "Customer",
|
||||
},
|
||||
{"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"},
|
||||
{"title": "Newsletter", "route": "/newsletters", "reference_doctype": "Newsletter"},
|
||||
{
|
||||
"title": _("Material Request"),
|
||||
"title": "Material Request",
|
||||
"route": "/material-requests",
|
||||
"reference_doctype": "Material Request",
|
||||
"role": "Customer",
|
||||
},
|
||||
{"title": _("Appointment Booking"), "route": "/book_appointment"},
|
||||
{"title": "Appointment Booking", "route": "/book_appointment"},
|
||||
]
|
||||
|
||||
default_roles = [
|
||||
|
||||
@@ -80,7 +80,7 @@ class MaintenanceSchedule(TransactionBase):
|
||||
self.update_amc_date(serial_nos, d.end_date)
|
||||
|
||||
no_email_sp = []
|
||||
if d.sales_person not in email_map:
|
||||
if d.sales_person and d.sales_person not in email_map:
|
||||
sp = frappe.get_doc("Sales Person", d.sales_person)
|
||||
try:
|
||||
email_map[d.sales_person] = sp.get_email_id()
|
||||
@@ -94,12 +94,11 @@ class MaintenanceSchedule(TransactionBase):
|
||||
).format(self.owner, "<br>" + "<br>".join(no_email_sp))
|
||||
)
|
||||
|
||||
scheduled_date = frappe.db.sql(
|
||||
"""select scheduled_date from
|
||||
`tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and
|
||||
parent=%s""",
|
||||
(d.sales_person, d.item_code, self.name),
|
||||
as_dict=1,
|
||||
scheduled_date = frappe.db.get_all(
|
||||
"Maintenance Schedule Detail",
|
||||
{"parent": self.name, "item_code": d.item_code},
|
||||
["scheduled_date"],
|
||||
as_list=False,
|
||||
)
|
||||
|
||||
for key in scheduled_date:
|
||||
@@ -195,8 +194,6 @@ class MaintenanceSchedule(TransactionBase):
|
||||
throw(_("Please select Start Date and End Date for Item {0}").format(d.item_code))
|
||||
elif not d.no_of_visits:
|
||||
throw(_("Please mention no of visits required"))
|
||||
elif not d.sales_person:
|
||||
throw(_("Please select a Sales Person for item: {0}").format(d.item_name))
|
||||
|
||||
if getdate(d.start_date) >= getdate(d.end_date):
|
||||
throw(_("Start date should be less than end date for Item {0}").format(d.item_code))
|
||||
@@ -392,16 +389,28 @@ def get_serial_nos_from_schedule(item_code, schedule=None):
|
||||
def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def condition(doc):
|
||||
if s_id:
|
||||
return doc.name == s_id
|
||||
elif item_name:
|
||||
return doc.item_name == item_name
|
||||
|
||||
return True
|
||||
|
||||
def update_status_and_detail(source, target, parent):
|
||||
target.maintenance_type = "Scheduled"
|
||||
target.maintenance_schedule_detail = s_id
|
||||
|
||||
def update_serial(source, target, parent):
|
||||
serial_nos = get_serial_nos(target.serial_no)
|
||||
if len(serial_nos) == 1:
|
||||
target.serial_no = serial_nos[0]
|
||||
else:
|
||||
target.serial_no = ""
|
||||
if source.item_reference:
|
||||
if serial_nos := frappe.db.get_value(
|
||||
"Maintenance Schedule Item", source.item_reference, "serial_no"
|
||||
):
|
||||
serial_nos = serial_nos.split("\n")
|
||||
|
||||
if len(serial_nos) == 1:
|
||||
target.serial_no = serial_nos[0]
|
||||
else:
|
||||
target.serial_no = ""
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
"Maintenance Schedule",
|
||||
@@ -413,10 +422,13 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No
|
||||
"validation": {"docstatus": ["=", 1]},
|
||||
"postprocess": update_status_and_detail,
|
||||
},
|
||||
"Maintenance Schedule Item": {
|
||||
"Maintenance Schedule Detail": {
|
||||
"doctype": "Maintenance Visit Purpose",
|
||||
"condition": lambda doc: doc.item_name == item_name if item_name else True,
|
||||
"field_map": {"sales_person": "service_person"},
|
||||
"condition": condition,
|
||||
"field_map": {
|
||||
"sales_person": "service_person",
|
||||
"name": "maintenance_schedule_detail",
|
||||
},
|
||||
"postprocess": update_serial,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -23,20 +23,39 @@ class MaintenanceVisit(TransactionBase):
|
||||
frappe.throw(_("Add Items in the Purpose Table"), title=_("Purposes Required"))
|
||||
|
||||
def validate_maintenance_date(self):
|
||||
if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
|
||||
item_ref = frappe.db.get_value(
|
||||
"Maintenance Schedule Detail", self.maintenance_schedule_detail, "item_reference"
|
||||
)
|
||||
if item_ref:
|
||||
start_date, end_date = frappe.db.get_value(
|
||||
"Maintenance Schedule Item", item_ref, ["start_date", "end_date"]
|
||||
if self.maintenance_type == "Scheduled":
|
||||
if self.maintenance_schedule_detail:
|
||||
item_ref = frappe.db.get_value(
|
||||
"Maintenance Schedule Detail", self.maintenance_schedule_detail, "item_reference"
|
||||
)
|
||||
if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(
|
||||
self.mntc_date
|
||||
) > get_datetime(end_date):
|
||||
frappe.throw(
|
||||
_("Date must be between {0} and {1}").format(format_date(start_date), format_date(end_date))
|
||||
if item_ref:
|
||||
start_date, end_date = frappe.db.get_value(
|
||||
"Maintenance Schedule Item", item_ref, ["start_date", "end_date"]
|
||||
)
|
||||
if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(
|
||||
self.mntc_date
|
||||
) > get_datetime(end_date):
|
||||
frappe.throw(
|
||||
_("Date must be between {0} and {1}").format(format_date(start_date), format_date(end_date))
|
||||
)
|
||||
else:
|
||||
for purpose in self.purposes:
|
||||
if purpose.maintenance_schedule_detail:
|
||||
item_ref = frappe.db.get_value(
|
||||
"Maintenance Schedule Detail", purpose.maintenance_schedule_detail, "item_reference"
|
||||
)
|
||||
if item_ref:
|
||||
start_date, end_date = frappe.db.get_value(
|
||||
"Maintenance Schedule Item", item_ref, ["start_date", "end_date"]
|
||||
)
|
||||
if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(
|
||||
self.mntc_date
|
||||
) > get_datetime(end_date):
|
||||
frappe.throw(
|
||||
_("Date must be between {0} and {1}").format(
|
||||
format_date(start_date), format_date(end_date)
|
||||
)
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_serial_no()
|
||||
@@ -49,6 +68,7 @@ class MaintenanceVisit(TransactionBase):
|
||||
if not cancel:
|
||||
status = self.completion_status
|
||||
actual_date = self.mntc_date
|
||||
|
||||
if self.maintenance_schedule_detail:
|
||||
frappe.db.set_value(
|
||||
"Maintenance Schedule Detail", self.maintenance_schedule_detail, "completion_status", status
|
||||
@@ -56,6 +76,21 @@ class MaintenanceVisit(TransactionBase):
|
||||
frappe.db.set_value(
|
||||
"Maintenance Schedule Detail", self.maintenance_schedule_detail, "actual_date", actual_date
|
||||
)
|
||||
else:
|
||||
for purpose in self.purposes:
|
||||
if purpose.maintenance_schedule_detail:
|
||||
frappe.db.set_value(
|
||||
"Maintenance Schedule Detail",
|
||||
purpose.maintenance_schedule_detail,
|
||||
"completion_status",
|
||||
status,
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Maintenance Schedule Detail",
|
||||
purpose.maintenance_schedule_detail,
|
||||
"actual_date",
|
||||
actual_date,
|
||||
)
|
||||
|
||||
def update_customer_issue(self, flag):
|
||||
if not self.maintenance_schedule:
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"work_details",
|
||||
"work_done",
|
||||
"prevdoc_doctype",
|
||||
"prevdoc_docname"
|
||||
"prevdoc_docname",
|
||||
"maintenance_schedule_detail"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -49,6 +50,8 @@
|
||||
"options": "Serial No"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.description",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
@@ -56,7 +59,6 @@
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "300px",
|
||||
"reqd": 1,
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
@@ -103,12 +105,19 @@
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "maintenance_schedule_detail",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Maintenance Schedule Detail",
|
||||
"options": "Maintenance Schedule Detail"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-02-27 11:09:33.114458",
|
||||
"modified": "2024-01-05 21:46:53.239830",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Maintenance",
|
||||
"name": "Maintenance Visit Purpose",
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"last_synced_on": "2020-07-21 16:57:09.767009",
|
||||
"modified": "2020-07-21 16:57:55.719802",
|
||||
"modified": "2024-01-10 12:21:25.134075",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Completed Operation",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"parent_document_type": "Work Order",
|
||||
"time_interval": "Quarterly",
|
||||
"timeseries": 1,
|
||||
"timespan": "Last Year",
|
||||
|
||||
@@ -86,10 +86,12 @@ def get_ancestor_boms(new_bom: str, bom_list: Optional[List] = None) -> List:
|
||||
if new_bom == d.parent:
|
||||
frappe.throw(_("BOM recursion: {0} cannot be child of {1}").format(new_bom, d.parent))
|
||||
|
||||
bom_list.append(d.parent)
|
||||
if d.parent not in tuple(bom_list):
|
||||
bom_list.append(d.parent)
|
||||
|
||||
get_ancestor_boms(d.parent, bom_list)
|
||||
|
||||
return list(set(bom_list))
|
||||
return bom_list
|
||||
|
||||
|
||||
def update_new_bom_in_bom_items(unit_cost: float, current_bom: str, new_bom: str) -> None:
|
||||
|
||||
@@ -57,6 +57,68 @@ class TestBOMUpdateLog(FrappeTestCase):
|
||||
log.reload()
|
||||
self.assertEqual(log.status, "Completed")
|
||||
|
||||
def test_bom_replace_for_root_bom(self):
|
||||
"""
|
||||
- B-Item A (Root Item)
|
||||
- B-Item B
|
||||
- B-Item C
|
||||
- B-Item D
|
||||
- B-Item E
|
||||
- B-Item F
|
||||
|
||||
Create New BOM for B-Item E with B-Item G and replace it in the above BOM.
|
||||
"""
|
||||
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
items = ["B-Item A", "B-Item B", "B-Item C", "B-Item D", "B-Item E", "B-Item F", "B-Item G"]
|
||||
|
||||
for item_code in items:
|
||||
if not frappe.db.exists("Item", item_code):
|
||||
make_item(item_code)
|
||||
|
||||
for item_code in items:
|
||||
remove_bom(item_code)
|
||||
|
||||
bom_tree = {
|
||||
"B-Item A": {"B-Item B": {"B-Item C": {}}, "B-Item D": {"B-Item E": {"B-Item F": {}}}}
|
||||
}
|
||||
|
||||
root_bom = create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
exploded_items = frappe.get_all(
|
||||
"BOM Explosion Item", filters={"parent": root_bom.name}, fields=["item_code"]
|
||||
)
|
||||
|
||||
exploded_items = [item.item_code for item in exploded_items]
|
||||
expected_exploded_items = ["B-Item C", "B-Item F"]
|
||||
self.assertEqual(sorted(exploded_items), sorted(expected_exploded_items))
|
||||
|
||||
old_bom = frappe.db.get_value("BOM", {"item": "B-Item E"}, "name")
|
||||
bom_tree = {"B-Item E": {"B-Item G": {}}}
|
||||
|
||||
new_bom = create_nested_bom(bom_tree, prefix="")
|
||||
enqueue_replace_bom(boms=frappe._dict(current_bom=old_bom, new_bom=new_bom.name))
|
||||
|
||||
exploded_items = frappe.get_all(
|
||||
"BOM Explosion Item", filters={"parent": root_bom.name}, fields=["item_code"]
|
||||
)
|
||||
|
||||
exploded_items = [item.item_code for item in exploded_items]
|
||||
expected_exploded_items = ["B-Item C", "B-Item G"]
|
||||
self.assertEqual(sorted(exploded_items), sorted(expected_exploded_items))
|
||||
|
||||
|
||||
def remove_bom(item_code):
|
||||
boms = frappe.get_all("BOM", fields=["docstatus", "name"], filters={"item": item_code})
|
||||
|
||||
for row in boms:
|
||||
if row.docstatus == 1:
|
||||
frappe.get_doc("BOM", row.name).cancel()
|
||||
|
||||
frappe.delete_doc("BOM", row.name)
|
||||
|
||||
|
||||
def update_cost_in_all_boms_in_test():
|
||||
"""
|
||||
|
||||
@@ -570,6 +570,10 @@ class ProductionPlan(Document):
|
||||
"project": self.project,
|
||||
}
|
||||
|
||||
key = (d.item_code, d.sales_order, d.warehouse)
|
||||
if not d.sales_order:
|
||||
key = (d.name, d.item_code, d.warehouse)
|
||||
|
||||
if not item_details["project"] and d.sales_order:
|
||||
item_details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
|
||||
|
||||
@@ -578,12 +582,9 @@ class ProductionPlan(Document):
|
||||
item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details
|
||||
else:
|
||||
item_details.update(
|
||||
{
|
||||
"qty": flt(item_dict.get((d.item_code, d.sales_order, d.warehouse), {}).get("qty"))
|
||||
+ (flt(d.planned_qty) - flt(d.ordered_qty))
|
||||
}
|
||||
{"qty": flt(item_dict.get(key, {}).get("qty")) + (flt(d.planned_qty) - flt(d.ordered_qty))}
|
||||
)
|
||||
item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details
|
||||
item_dict[key] = item_details
|
||||
|
||||
return item_dict
|
||||
|
||||
|
||||
@@ -661,7 +661,7 @@ class TestProductionPlan(FrappeTestCase):
|
||||
items_data = pln.get_production_items()
|
||||
|
||||
# Update qty
|
||||
items_data[(item, None, None)]["qty"] = qty
|
||||
items_data[(pln.po_items[0].name, item, None)]["qty"] = qty
|
||||
|
||||
# Create and Submit Work Order for each item in items_data
|
||||
for key, item in items_data.items():
|
||||
@@ -1511,6 +1511,45 @@ class TestProductionPlan(FrappeTestCase):
|
||||
for d in mr_items:
|
||||
self.assertEqual(d.get("quantity"), 1000.0)
|
||||
|
||||
def test_fg_item_quantity(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.utils import get_or_make_bin
|
||||
|
||||
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||
rm_item = make_item(properties={"is_stock_item": 1}).name
|
||||
|
||||
make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
|
||||
|
||||
pln = create_production_plan(item_code=fg_item, planned_qty=10, do_not_submit=1)
|
||||
|
||||
pln.append(
|
||||
"po_items",
|
||||
{
|
||||
"item_code": rm_item,
|
||||
"planned_qty": 20,
|
||||
"bom_no": pln.po_items[0].bom_no,
|
||||
"warehouse": pln.po_items[0].warehouse,
|
||||
"planned_start_date": add_to_date(nowdate(), days=1),
|
||||
},
|
||||
)
|
||||
pln.submit()
|
||||
wo_qty = {}
|
||||
|
||||
for row in pln.po_items:
|
||||
wo_qty[row.name] = row.planned_qty
|
||||
|
||||
pln.make_work_order()
|
||||
|
||||
work_orders = frappe.get_all(
|
||||
"Work Order",
|
||||
fields=["qty", "production_plan_item as name"],
|
||||
filters={"production_plan": pln.name},
|
||||
)
|
||||
self.assertEqual(len(work_orders), 2)
|
||||
|
||||
for row in work_orders:
|
||||
self.assertEqual(row.qty, wo_qty[row.name])
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
"""
|
||||
|
||||
@@ -389,6 +389,9 @@ def make_sales_invoice(source_name, item_code=None, customer=None, currency=None
|
||||
"timesheets",
|
||||
{
|
||||
"time_sheet": timesheet.name,
|
||||
"project_name": time_log.project_name,
|
||||
"from_time": time_log.from_time,
|
||||
"to_time": time_log.to_time,
|
||||
"billing_hours": time_log.billing_hours,
|
||||
"billing_amount": time_log.billing_amount,
|
||||
"timesheet_detail": time_log.name,
|
||||
|
||||
@@ -739,7 +739,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
if (me.frm.doc.price_list_currency == company_currency) {
|
||||
me.frm.set_value('plc_conversion_rate', 1.0);
|
||||
}
|
||||
if (company_doc.default_letter_head) {
|
||||
if (company_doc && company_doc.default_letter_head) {
|
||||
if(me.frm.fields_dict.letter_head) {
|
||||
me.frm.set_value("letter_head", company_doc.default_letter_head);
|
||||
}
|
||||
|
||||
@@ -706,7 +706,9 @@ def make_contact(args, is_primary_contact=1):
|
||||
else:
|
||||
values.update(
|
||||
{
|
||||
"first_name": args.get("customer_name"),
|
||||
"first_name": args.get("customer_name")
|
||||
if args.doctype == "Customer"
|
||||
else args.get("supplier_name"),
|
||||
"company_name": args.get(party_name_key),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -288,15 +288,16 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
||||
)
|
||||
|
||||
# sales team
|
||||
for d in customer.get("sales_team") or []:
|
||||
target.append(
|
||||
"sales_team",
|
||||
{
|
||||
"sales_person": d.sales_person,
|
||||
"allocated_percentage": d.allocated_percentage or None,
|
||||
"commission_rate": d.commission_rate,
|
||||
},
|
||||
)
|
||||
if not target.get("sales_team"):
|
||||
for d in customer.get("sales_team") or []:
|
||||
target.append(
|
||||
"sales_team",
|
||||
{
|
||||
"sales_person": d.sales_person,
|
||||
"allocated_percentage": d.allocated_percentage or None,
|
||||
"commission_rate": d.commission_rate,
|
||||
},
|
||||
)
|
||||
|
||||
target.flags.ignore_permissions = ignore_permissions
|
||||
target.delivery_date = nowdate()
|
||||
|
||||
@@ -432,17 +432,17 @@ class SalesOrder(SellingController):
|
||||
|
||||
def set_indicator(self):
|
||||
"""Set indicator for portal"""
|
||||
if self.per_billed < 100 and self.per_delivered < 100:
|
||||
self.indicator_color = "orange"
|
||||
self.indicator_title = _("Not Paid and Not Delivered")
|
||||
self.indicator_color = {
|
||||
"Draft": "red",
|
||||
"On Hold": "orange",
|
||||
"To Deliver and Bill": "orange",
|
||||
"To Bill": "orange",
|
||||
"To Deliver": "orange",
|
||||
"Completed": "green",
|
||||
"Cancelled": "red",
|
||||
}.get(self.status, "blue")
|
||||
|
||||
elif self.per_billed == 100 and self.per_delivered < 100:
|
||||
self.indicator_color = "orange"
|
||||
self.indicator_title = _("Paid and Not Delivered")
|
||||
|
||||
else:
|
||||
self.indicator_color = "green"
|
||||
self.indicator_title = _("Paid")
|
||||
self.indicator_title = _(self.status)
|
||||
|
||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
|
||||
|
||||
@@ -616,8 +616,8 @@
|
||||
"fieldname": "relieving_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Relieving Date",
|
||||
"no_copy": 1,
|
||||
"mandatory_depends_on": "eval:doc.status == \"Left\"",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "relieving_date",
|
||||
"oldfieldtype": "Date"
|
||||
},
|
||||
@@ -822,12 +822,14 @@
|
||||
"icon": "fa fa-user",
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-04 10:57:05.174592",
|
||||
"modified": "2024-01-03 17:36:20.984421",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Employee",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"nsm_parent_field": "reports_to",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -860,7 +862,6 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
@@ -871,4 +872,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "employee_name"
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ class ClosingStockBalance(Document):
|
||||
& (
|
||||
(table.from_date.between(self.from_date, self.to_date))
|
||||
| (table.to_date.between(self.from_date, self.to_date))
|
||||
| (table.from_date >= self.from_date and table.to_date <= self.to_date)
|
||||
| (table.from_date >= self.from_date and table.to_date >= self.to_date)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1435,6 +1435,25 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
returned_dn.reload()
|
||||
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0)
|
||||
|
||||
def test_internal_transfer_for_non_stock_item(self):
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
|
||||
|
||||
item = make_item(properties={"is_stock_item": 0}).name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
target = "Stores - _TC"
|
||||
company = "_Test Company"
|
||||
customer = create_internal_customer(represents_company=company)
|
||||
rate = 100
|
||||
|
||||
so = make_sales_order(item_code=item, qty=1, rate=rate, customer=customer, warehouse=warehouse)
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.items[0].target_warehouse = target
|
||||
dn.save().submit()
|
||||
|
||||
self.assertEqual(so.items[0].rate, rate)
|
||||
self.assertEqual(dn.items[0].rate, so.items[0].rate)
|
||||
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
|
||||
@@ -281,7 +281,7 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
|
||||
dimensions = get_document_wise_inventory_dimensions(doc.doctype)
|
||||
filter_dimensions = []
|
||||
for row in dimensions:
|
||||
if row.type_of_transaction:
|
||||
if row.type_of_transaction and row.type_of_transaction != "Both":
|
||||
if (
|
||||
row.type_of_transaction == "Inward"
|
||||
if doc.docstatus == 1
|
||||
|
||||
@@ -429,6 +429,14 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
)
|
||||
|
||||
warehouse = create_warehouse("Negative Stock Warehouse")
|
||||
|
||||
doc = make_stock_entry(item_code=item_code, source=warehouse, qty=10, do_not_submit=True)
|
||||
doc.items[0].inv_site = "Site 1"
|
||||
self.assertRaises(frappe.ValidationError, doc.submit)
|
||||
doc.reload()
|
||||
if doc.docstatus == 1:
|
||||
doc.cancel()
|
||||
|
||||
doc = make_stock_entry(item_code=item_code, target=warehouse, qty=10, do_not_submit=True)
|
||||
|
||||
doc.items[0].to_inv_site = "Site 1"
|
||||
|
||||
@@ -203,6 +203,7 @@
|
||||
"label": "Allow Alternative Item"
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"bold": 1,
|
||||
"default": "1",
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
@@ -240,6 +241,7 @@
|
||||
"label": "Standard Selling Rate"
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"default": "0",
|
||||
"fieldname": "is_fixed_asset",
|
||||
"fieldtype": "Check",
|
||||
@@ -247,6 +249,7 @@
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"depends_on": "is_fixed_asset",
|
||||
"fieldname": "asset_category",
|
||||
"fieldtype": "Link",
|
||||
@@ -897,7 +900,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2023-09-18 15:41:32.688051",
|
||||
"modified": "2024-01-08 18:09:30.225085",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -167,7 +167,8 @@ class LandedCostVoucher(Document):
|
||||
for d in self.get("purchase_receipts"):
|
||||
doc = frappe.get_doc(d.receipt_document_type, d.receipt_document)
|
||||
# check if there are {qty} assets created and linked to this receipt document
|
||||
self.validate_asset_qty_and_status(d.receipt_document_type, doc)
|
||||
if self.docstatus != 2:
|
||||
self.validate_asset_qty_and_status(d.receipt_document_type, doc)
|
||||
|
||||
# set landed cost voucher amount in pr item
|
||||
doc.set_landed_cost_voucher_amount()
|
||||
@@ -208,20 +209,20 @@ class LandedCostVoucher(Document):
|
||||
filters={receipt_document_type: item.receipt_document, "item_code": item.item_code},
|
||||
fields=["name", "docstatus"],
|
||||
)
|
||||
if not docs or len(docs) != item.qty:
|
||||
if not docs or len(docs) < item.qty:
|
||||
frappe.throw(
|
||||
_(
|
||||
"There are not enough asset created or linked to {0}. Please create or link {1} Assets with respective document."
|
||||
).format(item.receipt_document, item.qty)
|
||||
"There are only {0} asset created or linked to {1}. Please create or link {2} Assets with respective document."
|
||||
).format(len(docs), item.receipt_document, item.qty)
|
||||
)
|
||||
if docs:
|
||||
for d in docs:
|
||||
if d.docstatus == 1:
|
||||
frappe.throw(
|
||||
_(
|
||||
"{2} <b>{0}</b> has submitted Assets. Remove Item <b>{1}</b> from table to continue."
|
||||
"{0} <b>{1}</b> has submitted Assets. Remove Item <b>{2}</b> from table to continue."
|
||||
).format(
|
||||
item.receipt_document, item.item_code, item.receipt_document_type
|
||||
item.receipt_document_type, item.receipt_document, item.item_code
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -606,7 +606,7 @@ class PurchaseReceipt(BuyingController):
|
||||
):
|
||||
warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse)
|
||||
|
||||
if d.is_fixed_asset:
|
||||
if d.is_fixed_asset and d.landed_cost_voucher_amount:
|
||||
self.update_assets(d, d.valuation_rate)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
@@ -738,11 +738,14 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
for asset in assets:
|
||||
purchase_amount = flt(valuation_rate) * asset.asset_quantity
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "gross_purchase_amount", flt(valuation_rate) * asset.asset_quantity
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate) * asset.asset_quantity
|
||||
"Asset",
|
||||
asset.name,
|
||||
{
|
||||
"gross_purchase_amount": purchase_amount,
|
||||
"purchase_receipt_amount": purchase_amount,
|
||||
},
|
||||
)
|
||||
|
||||
def update_status(self, status):
|
||||
|
||||
@@ -71,16 +71,20 @@ class StockLedgerEntry(Document):
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"company": self.company,
|
||||
"sle": self.name,
|
||||
}
|
||||
)
|
||||
|
||||
sle = get_previous_sle(kwargs, extra_cond=extra_cond)
|
||||
qty_after_transaction = 0.0
|
||||
flt_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||
if sle:
|
||||
flt_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||
diff = sle.qty_after_transaction + flt(self.actual_qty)
|
||||
diff = flt(diff, flt_precision)
|
||||
if diff < 0 and abs(diff) > 0.0001:
|
||||
self.throw_validation_error(diff, dimensions)
|
||||
qty_after_transaction = sle.qty_after_transaction
|
||||
|
||||
diff = qty_after_transaction + flt(self.actual_qty)
|
||||
diff = flt(diff, flt_precision)
|
||||
if diff < 0 and abs(diff) > 0.0001:
|
||||
self.throw_validation_error(diff, dimensions)
|
||||
|
||||
def throw_validation_error(self, diff, dimensions):
|
||||
dimension_msg = _(", with the inventory {0}: {1}").format(
|
||||
|
||||
@@ -15,18 +15,15 @@ def transaction_processing(data, from_doctype, to_doctype):
|
||||
|
||||
length_of_data = len(deserialized_data)
|
||||
|
||||
if length_of_data > 10:
|
||||
frappe.msgprint(
|
||||
_("Started a background job to create {1} {0}").format(to_doctype, length_of_data)
|
||||
)
|
||||
frappe.enqueue(
|
||||
job,
|
||||
deserialized_data=deserialized_data,
|
||||
from_doctype=from_doctype,
|
||||
to_doctype=to_doctype,
|
||||
)
|
||||
else:
|
||||
job(deserialized_data, from_doctype, to_doctype)
|
||||
frappe.msgprint(
|
||||
_("Started a background job to create {1} {0}").format(to_doctype, length_of_data)
|
||||
)
|
||||
frappe.enqueue(
|
||||
job,
|
||||
deserialized_data=deserialized_data,
|
||||
from_doctype=from_doctype,
|
||||
to_doctype=to_doctype,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
Reference in New Issue
Block a user