Merge branch 'develop' into FIX-ISS-22-23-05936

This commit is contained in:
Sagar Sharma
2023-03-17 13:54:13 +05:30
committed by GitHub
12 changed files with 135 additions and 54 deletions

View File

@@ -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

View File

@@ -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 = [

View File

@@ -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):

View File

@@ -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",

View File

@@ -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",

View File

@@ -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()

View File

@@ -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")

View File

@@ -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");
}, },

View File

@@ -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
)
)

View File

@@ -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)

View File

@@ -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
) )

View File

@@ -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",