mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-22 06:29:20 +00:00
Merge branch 'develop' into FIX-ISS-22-23-05936
This commit is contained in:
5
.github/helper/install.sh
vendored
5
.github/helper/install.sh
vendored
@@ -8,8 +8,9 @@ sudo apt update && sudo apt install redis-server libcups2-dev
|
|||||||
|
|
||||||
pip install frappe-bench
|
pip install frappe-bench
|
||||||
|
|
||||||
|
githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
|
||||||
frappeuser=${FRAPPE_USER:-"frappe"}
|
frappeuser=${FRAPPE_USER:-"frappe"}
|
||||||
frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}}
|
frappebranch=${FRAPPE_BRANCH:-$githubbranch}
|
||||||
|
|
||||||
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
|
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
|
||||||
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
|
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
|
||||||
@@ -60,7 +61,7 @@ sed -i 's/schedule:/# schedule:/g' Procfile
|
|||||||
sed -i 's/socketio:/# socketio:/g' Procfile
|
sed -i 's/socketio:/# socketio:/g' Procfile
|
||||||
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
||||||
|
|
||||||
bench get-app payments
|
bench get-app payments --branch ${githubbranch%"-hotfix"}
|
||||||
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
||||||
|
|
||||||
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
|
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
|
||||||
|
|||||||
@@ -828,8 +828,8 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
["2030-12-31", 28630.14, 28630.14],
|
["2030-12-31", 28630.14, 28630.14],
|
||||||
["2031-12-31", 35684.93, 64315.07],
|
["2031-12-31", 35684.93, 64315.07],
|
||||||
["2032-12-31", 17842.47, 82157.54],
|
["2032-12-31", 17842.46, 82157.53],
|
||||||
["2033-06-06", 5342.46, 87500.0],
|
["2033-06-06", 5342.47, 87500.0],
|
||||||
]
|
]
|
||||||
|
|
||||||
schedules = [
|
schedules = [
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ class AssetDepreciationSchedule(Document):
|
|||||||
self.asset = asset_doc.name
|
self.asset = asset_doc.name
|
||||||
self.finance_book = row.finance_book
|
self.finance_book = row.finance_book
|
||||||
self.finance_book_id = row.idx
|
self.finance_book_id = row.idx
|
||||||
self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation
|
self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation or 0
|
||||||
self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked
|
self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked or 0
|
||||||
self.gross_purchase_amount = asset_doc.gross_purchase_amount
|
self.gross_purchase_amount = asset_doc.gross_purchase_amount
|
||||||
self.depreciation_method = row.depreciation_method
|
self.depreciation_method = row.depreciation_method
|
||||||
self.total_number_of_depreciations = row.total_number_of_depreciations
|
self.total_number_of_depreciations = row.total_number_of_depreciations
|
||||||
@@ -185,14 +185,14 @@ class AssetDepreciationSchedule(Document):
|
|||||||
):
|
):
|
||||||
asset_doc.validate_asset_finance_books(row)
|
asset_doc.validate_asset_finance_books(row)
|
||||||
|
|
||||||
value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
|
value_after_depreciation = self._get_value_after_depreciation_for_making_schedule(asset_doc, row)
|
||||||
row.value_after_depreciation = value_after_depreciation
|
row.value_after_depreciation = value_after_depreciation
|
||||||
|
|
||||||
if update_asset_finance_book_row:
|
if update_asset_finance_book_row:
|
||||||
row.db_update()
|
row.db_update()
|
||||||
|
|
||||||
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
|
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
|
||||||
asset_doc.number_of_depreciations_booked
|
self.number_of_depreciations_booked
|
||||||
)
|
)
|
||||||
|
|
||||||
has_pro_rata = asset_doc.check_is_pro_rata(row)
|
has_pro_rata = asset_doc.check_is_pro_rata(row)
|
||||||
@@ -235,13 +235,12 @@ class AssetDepreciationSchedule(Document):
|
|||||||
self.add_depr_schedule_row(
|
self.add_depr_schedule_row(
|
||||||
date_of_disposal,
|
date_of_disposal,
|
||||||
depreciation_amount,
|
depreciation_amount,
|
||||||
row.depreciation_method,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# For first row
|
# For first row
|
||||||
if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0:
|
if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
|
||||||
from_date = add_days(
|
from_date = add_days(
|
||||||
asset_doc.available_for_use_date, -1
|
asset_doc.available_for_use_date, -1
|
||||||
) # needed to calc depr amount for available_for_use_date too
|
) # needed to calc depr amount for available_for_use_date too
|
||||||
@@ -260,7 +259,7 @@ class AssetDepreciationSchedule(Document):
|
|||||||
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
|
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
|
||||||
asset_doc.to_date = add_months(
|
asset_doc.to_date = add_months(
|
||||||
asset_doc.available_for_use_date,
|
asset_doc.available_for_use_date,
|
||||||
(n + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
|
(n + self.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
|
||||||
)
|
)
|
||||||
|
|
||||||
depreciation_amount_without_pro_rata = depreciation_amount
|
depreciation_amount_without_pro_rata = depreciation_amount
|
||||||
@@ -298,7 +297,6 @@ class AssetDepreciationSchedule(Document):
|
|||||||
self.add_depr_schedule_row(
|
self.add_depr_schedule_row(
|
||||||
schedule_date,
|
schedule_date,
|
||||||
depreciation_amount,
|
depreciation_amount,
|
||||||
row.depreciation_method,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# to ensure that final accumulated depreciation amount is accurate
|
# to ensure that final accumulated depreciation amount is accurate
|
||||||
@@ -325,14 +323,12 @@ class AssetDepreciationSchedule(Document):
|
|||||||
self,
|
self,
|
||||||
schedule_date,
|
schedule_date,
|
||||||
depreciation_amount,
|
depreciation_amount,
|
||||||
depreciation_method,
|
|
||||||
):
|
):
|
||||||
self.append(
|
self.append(
|
||||||
"depreciation_schedule",
|
"depreciation_schedule",
|
||||||
{
|
{
|
||||||
"schedule_date": schedule_date,
|
"schedule_date": schedule_date,
|
||||||
"depreciation_amount": depreciation_amount,
|
"depreciation_amount": depreciation_amount,
|
||||||
"depreciation_method": depreciation_method,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -346,7 +342,7 @@ class AssetDepreciationSchedule(Document):
|
|||||||
straight_line_idx = [
|
straight_line_idx = [
|
||||||
d.idx
|
d.idx
|
||||||
for d in self.get("depreciation_schedule")
|
for d in self.get("depreciation_schedule")
|
||||||
if d.depreciation_method == "Straight Line" or d.depreciation_method == "Manual"
|
if self.depreciation_method == "Straight Line" or self.depreciation_method == "Manual"
|
||||||
]
|
]
|
||||||
|
|
||||||
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
||||||
@@ -377,16 +373,15 @@ class AssetDepreciationSchedule(Document):
|
|||||||
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
|
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_value_after_depreciation_for_making_schedule(self, asset_doc, fb_row):
|
||||||
|
if asset_doc.docstatus == 1 and fb_row.value_after_depreciation:
|
||||||
|
value_after_depreciation = flt(fb_row.value_after_depreciation)
|
||||||
|
else:
|
||||||
|
value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
||||||
|
self.opening_accumulated_depreciation
|
||||||
|
)
|
||||||
|
|
||||||
def _get_value_after_depreciation_for_making_schedule(asset_doc, fb_row):
|
return value_after_depreciation
|
||||||
if asset_doc.docstatus == 1 and fb_row.value_after_depreciation:
|
|
||||||
value_after_depreciation = flt(fb_row.value_after_depreciation)
|
|
||||||
else:
|
|
||||||
value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt(
|
|
||||||
asset_doc.opening_accumulated_depreciation
|
|
||||||
)
|
|
||||||
|
|
||||||
return value_after_depreciation
|
|
||||||
|
|
||||||
|
|
||||||
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
|
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
|
||||||
|
|||||||
@@ -12,8 +12,7 @@
|
|||||||
"column_break_3",
|
"column_break_3",
|
||||||
"accumulated_depreciation_amount",
|
"accumulated_depreciation_amount",
|
||||||
"journal_entry",
|
"journal_entry",
|
||||||
"make_depreciation_entry",
|
"make_depreciation_entry"
|
||||||
"depreciation_method"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -58,20 +57,11 @@
|
|||||||
"fieldname": "make_depreciation_entry",
|
"fieldname": "make_depreciation_entry",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Make Depreciation Entry"
|
"label": "Make Depreciation Entry"
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "depreciation_method",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Depreciation Method",
|
|
||||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-12-06 20:35:50.264281",
|
"modified": "2023-03-13 23:17:15.849950",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Depreciation Schedule",
|
"name": "Depreciation Schedule",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"transaction_settings_section",
|
"transaction_settings_section",
|
||||||
"po_required",
|
"po_required",
|
||||||
"pr_required",
|
"pr_required",
|
||||||
|
"over_order_allowance",
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
"maintain_same_rate",
|
"maintain_same_rate",
|
||||||
"set_landed_cost_based_on_purchase_invoice_rate",
|
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||||
@@ -156,6 +157,13 @@
|
|||||||
"fieldname": "set_landed_cost_based_on_purchase_invoice_rate",
|
"fieldname": "set_landed_cost_based_on_purchase_invoice_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Set Landed Cost Based on Purchase Invoice Rate"
|
"label": "Set Landed Cost Based on Purchase Invoice Rate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
|
||||||
|
"fieldname": "over_order_allowance",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Over Order Allowance (%)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@@ -163,7 +171,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-02-28 15:41:32.686805",
|
"modified": "2023-03-02 17:02:14.404622",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
|
|||||||
from erpnext.accounts.party import get_party_account, get_party_account_currency
|
from erpnext.accounts.party import get_party_account, get_party_account_currency
|
||||||
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
||||||
from erpnext.controllers.buying_controller import BuyingController
|
from erpnext.controllers.buying_controller import BuyingController
|
||||||
|
from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
|
||||||
|
validate_against_blanket_order,
|
||||||
|
)
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details
|
from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details
|
||||||
from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
|
from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
|
||||||
@@ -69,6 +72,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.validate_with_previous_doc()
|
self.validate_with_previous_doc()
|
||||||
self.validate_for_subcontracting()
|
self.validate_for_subcontracting()
|
||||||
self.validate_minimum_order_qty()
|
self.validate_minimum_order_qty()
|
||||||
|
validate_against_blanket_order(self)
|
||||||
|
|
||||||
if self.is_old_subcontracting_flow:
|
if self.is_old_subcontracting_flow:
|
||||||
self.validate_bom_for_subcontracting_items()
|
self.validate_bom_for_subcontracting_items()
|
||||||
|
|||||||
@@ -226,11 +226,11 @@ class TestWebsiteItem(unittest.TestCase):
|
|||||||
self.assertTrue(bool(data.product_info["price"]))
|
self.assertTrue(bool(data.product_info["price"]))
|
||||||
|
|
||||||
price_object = data.product_info["price"]
|
price_object = data.product_info["price"]
|
||||||
self.assertEqual(price_object.get("discount_percent"), 25)
|
self.assertEqual(price_object.get("discount_percent"), 25.0)
|
||||||
self.assertEqual(price_object.get("price_list_rate"), 750)
|
self.assertEqual(price_object.get("price_list_rate"), 750)
|
||||||
self.assertEqual(price_object.get("formatted_mrp"), "₹ 1,000.00")
|
self.assertEqual(price_object.get("formatted_mrp"), "₹ 1,000.00")
|
||||||
self.assertEqual(price_object.get("formatted_price"), "₹ 750.00")
|
self.assertEqual(price_object.get("formatted_price"), "₹ 750.00")
|
||||||
self.assertEqual(price_object.get("formatted_discount_percent"), "25%")
|
self.assertEqual(price_object.get("formatted_discount_percent"), "25.0%")
|
||||||
|
|
||||||
# switch to admin and disable show price
|
# switch to admin and disable show price
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ frappe.ui.form.on('Blanket Order', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
frm.custom_make_buttons = {
|
||||||
|
'Purchase Order': 'Purchase Order',
|
||||||
|
'Sales Order': 'Sales Order',
|
||||||
|
'Quotation': 'Quotation',
|
||||||
|
};
|
||||||
|
|
||||||
frm.add_fetch("customer", "customer_name", "customer_name");
|
frm.add_fetch("customer", "customer_name", "customer_name");
|
||||||
frm.add_fetch("supplier", "supplier_name", "supplier_name");
|
frm.add_fetch("supplier", "supplier_name", "supplier_name");
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import flt, getdate
|
from frappe.utils import flt, getdate
|
||||||
|
|
||||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||||
@@ -29,21 +30,23 @@ class BlanketOrder(Document):
|
|||||||
|
|
||||||
def update_ordered_qty(self):
|
def update_ordered_qty(self):
|
||||||
ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order"
|
ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order"
|
||||||
|
|
||||||
|
trans = frappe.qb.DocType(ref_doctype)
|
||||||
|
trans_item = frappe.qb.DocType(f"{ref_doctype} Item")
|
||||||
|
|
||||||
item_ordered_qty = frappe._dict(
|
item_ordered_qty = frappe._dict(
|
||||||
frappe.db.sql(
|
(
|
||||||
"""
|
frappe.qb.from_(trans_item)
|
||||||
select trans_item.item_code, sum(trans_item.stock_qty) as qty
|
.from_(trans)
|
||||||
from `tab{0} Item` trans_item, `tab{0}` trans
|
.select(trans_item.item_code, Sum(trans_item.stock_qty).as_("qty"))
|
||||||
where trans.name = trans_item.parent
|
.where(
|
||||||
and trans_item.blanket_order=%s
|
(trans.name == trans_item.parent)
|
||||||
and trans.docstatus=1
|
& (trans_item.blanket_order == self.name)
|
||||||
and trans.status not in ('Closed', 'Stopped')
|
& (trans.docstatus == 1)
|
||||||
group by trans_item.item_code
|
& (trans.status.notin(["Stopped", "Closed"]))
|
||||||
""".format(
|
)
|
||||||
ref_doctype
|
.groupby(trans_item.item_code)
|
||||||
),
|
).run()
|
||||||
self.name,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
@@ -79,7 +82,43 @@ def make_order(source_name):
|
|||||||
"doctype": doctype + " Item",
|
"doctype": doctype + " Item",
|
||||||
"field_map": {"rate": "blanket_order_rate", "parent": "blanket_order"},
|
"field_map": {"rate": "blanket_order_rate", "parent": "blanket_order"},
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
|
"condition": lambda item: (flt(item.qty) - flt(item.ordered_qty)) > 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return target_doc
|
return target_doc
|
||||||
|
|
||||||
|
|
||||||
|
def validate_against_blanket_order(order_doc):
|
||||||
|
if order_doc.doctype in ("Sales Order", "Purchase Order"):
|
||||||
|
order_data = {}
|
||||||
|
|
||||||
|
for item in order_doc.get("items"):
|
||||||
|
if item.against_blanket_order and item.blanket_order:
|
||||||
|
if item.blanket_order in order_data:
|
||||||
|
if item.item_code in order_data[item.blanket_order]:
|
||||||
|
order_data[item.blanket_order][item.item_code] += item.qty
|
||||||
|
else:
|
||||||
|
order_data[item.blanket_order][item.item_code] = item.qty
|
||||||
|
else:
|
||||||
|
order_data[item.blanket_order] = {item.item_code: item.qty}
|
||||||
|
|
||||||
|
if order_data:
|
||||||
|
allowance = flt(
|
||||||
|
frappe.db.get_single_value(
|
||||||
|
"Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings",
|
||||||
|
"over_order_allowance",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for bo_name, item_data in order_data.items():
|
||||||
|
bo_doc = frappe.get_doc("Blanket Order", bo_name)
|
||||||
|
for item in bo_doc.get("items"):
|
||||||
|
if item.item_code in item_data:
|
||||||
|
remaining_qty = item.qty - item.ordered_qty
|
||||||
|
allowed_qty = remaining_qty + (remaining_qty * (allowance / 100))
|
||||||
|
if allowed_qty < item_data[item.item_code]:
|
||||||
|
frappe.throw(
|
||||||
|
_("Item {0} cannot be ordered more than {1} against Blanket Order {2}.").format(
|
||||||
|
item.item_code, allowed_qty, bo_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -63,6 +63,33 @@ class TestBlanketOrder(FrappeTestCase):
|
|||||||
po1.currency = get_company_currency(po1.company)
|
po1.currency = get_company_currency(po1.company)
|
||||||
self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
|
self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
|
||||||
|
|
||||||
|
def test_over_order_allowance(self):
|
||||||
|
# Sales Order
|
||||||
|
bo = make_blanket_order(blanket_order_type="Selling", quantity=100)
|
||||||
|
|
||||||
|
frappe.flags.args.doctype = "Sales Order"
|
||||||
|
so = make_order(bo.name)
|
||||||
|
so.currency = get_company_currency(so.company)
|
||||||
|
so.delivery_date = today()
|
||||||
|
so.items[0].qty = 110
|
||||||
|
self.assertRaises(frappe.ValidationError, so.submit)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10)
|
||||||
|
so.submit()
|
||||||
|
|
||||||
|
# Purchase Order
|
||||||
|
bo = make_blanket_order(blanket_order_type="Purchasing", quantity=100)
|
||||||
|
|
||||||
|
frappe.flags.args.doctype = "Purchase Order"
|
||||||
|
po = make_order(bo.name)
|
||||||
|
po.currency = get_company_currency(po.company)
|
||||||
|
po.schedule_date = today()
|
||||||
|
po.items[0].qty = 110
|
||||||
|
self.assertRaises(frappe.ValidationError, po.submit)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10)
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
|
||||||
def make_blanket_order(**args):
|
def make_blanket_order(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
|||||||
)
|
)
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
from erpnext.controllers.selling_controller import SellingController
|
from erpnext.controllers.selling_controller import SellingController
|
||||||
|
from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
|
||||||
|
validate_against_blanket_order,
|
||||||
|
)
|
||||||
from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
||||||
get_items_for_material_requests,
|
get_items_for_material_requests,
|
||||||
)
|
)
|
||||||
@@ -52,6 +55,7 @@ class SalesOrder(SellingController):
|
|||||||
self.validate_warehouse()
|
self.validate_warehouse()
|
||||||
self.validate_drop_ship()
|
self.validate_drop_ship()
|
||||||
self.validate_serial_no_based_delivery()
|
self.validate_serial_no_based_delivery()
|
||||||
|
validate_against_blanket_order(self)
|
||||||
validate_inter_company_party(
|
validate_inter_company_party(
|
||||||
self.doctype, self.customer, self.company, self.inter_company_order_reference
|
self.doctype, self.customer, self.company, self.inter_company_order_reference
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"so_required",
|
"so_required",
|
||||||
"dn_required",
|
"dn_required",
|
||||||
"sales_update_frequency",
|
"sales_update_frequency",
|
||||||
|
"over_order_allowance",
|
||||||
"column_break_5",
|
"column_break_5",
|
||||||
"allow_multiple_items",
|
"allow_multiple_items",
|
||||||
"allow_against_multiple_purchase_orders",
|
"allow_against_multiple_purchase_orders",
|
||||||
@@ -179,6 +180,12 @@
|
|||||||
"fieldname": "allow_sales_order_creation_for_expired_quotation",
|
"fieldname": "allow_sales_order_creation_for_expired_quotation",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow Sales Order Creation For Expired Quotation"
|
"label": "Allow Sales Order Creation For Expired Quotation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
|
||||||
|
"fieldname": "over_order_allowance",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Over Order Allowance (%)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@@ -186,7 +193,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-02-04 12:37:53.380857",
|
"modified": "2023-03-03 11:16:54.333615",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Selling Settings",
|
"name": "Selling Settings",
|
||||||
|
|||||||
Reference in New Issue
Block a user