Merge branch 'develop' into fg_based_operating_cost

This commit is contained in:
Vishal Dhayagude 2023-01-17 13:02:25 +05:30 committed by GitHub
commit f19212b5c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 156 additions and 82 deletions

View File

@ -66,7 +66,8 @@ ignore =
F841, F841,
E713, E713,
E712, E712,
B023 B023,
B028
max-line-length = 200 max-line-length = 200

View File

@ -6,6 +6,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"api_details_section", "api_details_section",
"disabled",
"service_provider", "service_provider",
"api_endpoint", "api_endpoint",
"url", "url",
@ -77,12 +78,18 @@
"label": "Service Provider", "label": "Service Provider",
"options": "frankfurter.app\nexchangerate.host\nCustom", "options": "frankfurter.app\nexchangerate.host\nCustom",
"reqd": 1 "reqd": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2022-01-10 15:51:14.521174", "modified": "2023-01-09 12:19:03.955906",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Currency Exchange Settings", "name": "Currency Exchange Settings",

View File

@ -334,7 +334,7 @@ class PaymentReconciliation(Document):
) )
# Account Currency has balance # Account Currency has balance
dr_or_cr = "debit" if self.party_type == "Customer" else "debit" dr_or_cr = "debit" if self.party_type == "Customer" else "credit"
reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
journal_account = frappe._dict( journal_account = frappe._dict(

View File

@ -13,38 +13,24 @@ frappe.query_reports["Work Order Summary"] = {
reqd: 1 reqd: 1
}, },
{ {
fieldname: "fiscal_year", label: __("Based On"),
label: __("Fiscal Year"), fieldname:"based_on",
fieldtype: "Link", fieldtype: "Select",
options: "Fiscal Year", options: "Creation Date\nPlanned Date\nActual Date",
default: frappe.defaults.get_user_default("fiscal_year"), default: "Creation Date"
reqd: 1,
on_change: function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year;
if (!fiscal_year) {
return;
}
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
frappe.query_report.set_filter_value({
from_date: fy.year_start_date,
to_date: fy.year_end_date
});
});
}
}, },
{ {
label: __("From Posting Date"), label: __("From Posting Date"),
fieldname:"from_date", fieldname:"from_date",
fieldtype: "Date", fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"), default: frappe.datetime.add_months(frappe.datetime.get_today(), -3),
reqd: 1 reqd: 1
}, },
{ {
label: __("To Posting Date"), label: __("To Posting Date"),
fieldname:"to_date", fieldname:"to_date",
fieldtype: "Date", fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"), default: frappe.datetime.get_today(),
reqd: 1, reqd: 1,
}, },
{ {

View File

@ -31,6 +31,7 @@ def get_data(filters):
"sales_order", "sales_order",
"production_item", "production_item",
"qty", "qty",
"creation",
"produced_qty", "produced_qty",
"planned_start_date", "planned_start_date",
"planned_end_date", "planned_end_date",
@ -47,11 +48,17 @@ def get_data(filters):
if filters.get(field): if filters.get(field):
query_filters[field] = filters.get(field) query_filters[field] = filters.get(field)
if filters.get("based_on") == "Planned Date":
query_filters["planned_start_date"] = (">=", filters.get("from_date")) query_filters["planned_start_date"] = (">=", filters.get("from_date"))
query_filters["planned_end_date"] = ("<=", filters.get("to_date")) query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
elif filters.get("based_on") == "Actual Date":
query_filters["actual_start_date"] = (">=", filters.get("from_date"))
query_filters["actual_end_date"] = ("<=", filters.get("to_date"))
else:
query_filters["creation"] = ("between", [filters.get("from_date"), filters.get("to_date")])
data = frappe.get_all( data = frappe.get_all(
"Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc" "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc", debug=1
) )
res = [] res = []
@ -213,6 +220,12 @@ def get_columns(filters):
"options": "Sales Order", "options": "Sales Order",
"width": 90, "width": 90,
}, },
{
"label": _("Created On"),
"fieldname": "creation",
"fieldtype": "Date",
"width": 150,
},
{ {
"label": _("Planned Start Date"), "label": _("Planned Start Date"),
"fieldname": "planned_start_date", "fieldname": "planned_start_date",

View File

@ -194,7 +194,6 @@ erpnext.patches.v13_0.update_project_template_tasks
erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.convert_qi_parameter_to_link_field
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v13_0.update_payment_terms_outstanding
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v13_0.update_vehicle_no_reqd_condition
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
@ -292,6 +291,7 @@ erpnext.patches.v13_0.update_exchange_rate_settings
erpnext.patches.v14_0.delete_amazon_mws_doctype erpnext.patches.v14_0.delete_amazon_mws_doctype
erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr
erpnext.patches.v13_0.update_accounts_in_loan_docs erpnext.patches.v13_0.update_accounts_in_loan_docs
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v14_0.update_batch_valuation_flag erpnext.patches.v14_0.update_batch_valuation_flag
erpnext.patches.v14_0.delete_non_profit_doctypes erpnext.patches.v14_0.delete_non_profit_doctypes
erpnext.patches.v13_0.add_cost_center_in_loans erpnext.patches.v13_0.add_cost_center_in_loans

View File

@ -7,6 +7,7 @@ from erpnext.stock.stock_ledger import update_entries_after
def execute(): def execute():
doctypes_to_reload = [ doctypes_to_reload = [
("setup", "company"),
("stock", "repost_item_valuation"), ("stock", "repost_item_valuation"),
("stock", "stock_entry_detail"), ("stock", "stock_entry_detail"),
("stock", "purchase_receipt_item"), ("stock", "purchase_receipt_item"),

View File

@ -14,7 +14,6 @@ def get_data():
}, },
"internal_links": { "internal_links": {
"Quotation": ["items", "prevdoc_docname"], "Quotation": ["items", "prevdoc_docname"],
"Material Request": ["items", "material_request"],
}, },
"transactions": [ "transactions": [
{ {

View File

@ -81,6 +81,11 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No
if entries: if entries:
return flt(entries[0].exchange_rate) return flt(entries[0].exchange_rate)
if frappe.get_cached_value(
"Currency Exchange Settings", "Currency Exchange Settings", "disabled"
):
return 0.00
try: try:
cache = frappe.cache() cache = frappe.cache()
key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency) key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency)

View File

@ -4,7 +4,7 @@
import json import json
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
from itertools import groupby from itertools import groupby
from typing import Dict, List, Set from typing import Dict, List
import frappe import frappe
from frappe import _ from frappe import _
@ -41,7 +41,9 @@ class PickList(Document):
) )
def before_submit(self): def before_submit(self):
update_sales_orders = set() self.validate_picked_items()
def validate_picked_items(self):
for item in self.locations: for item in self.locations:
if self.scan_mode and item.picked_qty < item.stock_qty: if self.scan_mode and item.picked_qty < item.stock_qty:
frappe.throw( frappe.throw(
@ -50,17 +52,14 @@ class PickList(Document):
).format(item.idx, item.stock_qty - item.picked_qty, item.stock_uom), ).format(item.idx, item.stock_qty - item.picked_qty, item.stock_uom),
title=_("Pick List Incomplete"), title=_("Pick List Incomplete"),
) )
elif not self.scan_mode and item.picked_qty == 0:
if not self.scan_mode and item.picked_qty == 0:
# if the user has not entered any picked qty, set it to stock_qty, before submit # if the user has not entered any picked qty, set it to stock_qty, before submit
item.picked_qty = item.stock_qty item.picked_qty = item.stock_qty
if item.sales_order_item:
# update the picked_qty in SO Item
self.update_sales_order_item(item, item.picked_qty, item.item_code)
update_sales_orders.add(item.sales_order)
if not frappe.get_cached_value("Item", item.item_code, "has_serial_no"): if not frappe.get_cached_value("Item", item.item_code, "has_serial_no"):
continue continue
if not item.serial_no: if not item.serial_no:
frappe.throw( frappe.throw(
_("Row #{0}: {1} does not have any available serial numbers in {2}").format( _("Row #{0}: {1} does not have any available serial numbers in {2}").format(
@ -68,8 +67,8 @@ class PickList(Document):
), ),
title=_("Serial Nos Required"), title=_("Serial Nos Required"),
) )
if len(item.serial_no.split("\n")) == item.picked_qty:
continue if len(item.serial_no.split("\n")) != item.picked_qty:
frappe.throw( frappe.throw(
_( _(
"For item {0} at row {1}, count of serial numbers does not match with the picked quantity" "For item {0} at row {1}, count of serial numbers does not match with the picked quantity"
@ -77,49 +76,87 @@ class PickList(Document):
title=_("Quantity Mismatch"), title=_("Quantity Mismatch"),
) )
def on_submit(self):
self.update_bundle_picked_qty() self.update_bundle_picked_qty()
self.update_sales_order_picking_status(update_sales_orders) self.update_reference_qty()
self.update_sales_order_picking_status()
def before_cancel(self):
"""Deduct picked qty on cancelling pick list"""
updated_sales_orders = set()
for item in self.get("locations"):
if item.sales_order_item:
self.update_sales_order_item(item, -1 * item.picked_qty, item.item_code)
updated_sales_orders.add(item.sales_order)
def on_cancel(self):
self.update_bundle_picked_qty() self.update_bundle_picked_qty()
self.update_sales_order_picking_status(updated_sales_orders) self.update_reference_qty()
self.update_sales_order_picking_status()
def update_sales_order_item(self, item, picked_qty, item_code): def update_reference_qty(self):
item_table = "Sales Order Item" if not item.product_bundle_item else "Packed Item" packed_items = []
stock_qty_field = "stock_qty" if not item.product_bundle_item else "qty" so_items = []
already_picked, actual_qty = frappe.db.get_value( for item in self.locations:
item_table, if item.product_bundle_item:
item.sales_order_item, packed_items.append(item.sales_order_item)
["picked_qty", stock_qty_field], elif item.sales_order_item:
for_update=True, so_items.append(item.sales_order_item)
if packed_items:
self.update_packed_items_qty(packed_items)
if so_items:
self.update_sales_order_item_qty(so_items)
def update_packed_items_qty(self, packed_items):
picked_items = get_picked_items_qty(packed_items)
self.validate_picked_qty(picked_items)
picked_qty = frappe._dict()
for d in picked_items:
picked_qty[d.sales_order_item] = d.picked_qty
for packed_item in packed_items:
frappe.db.set_value(
"Packed Item",
packed_item,
"picked_qty",
flt(picked_qty.get(packed_item)),
update_modified=False,
) )
if self.docstatus == 1: def update_sales_order_item_qty(self, so_items):
if (((already_picked + picked_qty) / actual_qty) * 100) > ( picked_items = get_picked_items_qty(so_items)
100 + flt(frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")) self.validate_picked_qty(picked_items)
):
picked_qty = frappe._dict()
for d in picked_items:
picked_qty[d.sales_order_item] = d.picked_qty
for so_item in so_items:
frappe.db.set_value(
"Sales Order Item",
so_item,
"picked_qty",
flt(picked_qty.get(so_item)),
update_modified=False,
)
def update_sales_order_picking_status(self) -> None:
sales_orders = []
for row in self.locations:
if row.sales_order and row.sales_order not in sales_orders:
sales_orders.append(row.sales_order)
for sales_order in sales_orders:
frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()
def validate_picked_qty(self, data):
over_delivery_receipt_allowance = 100 + flt(
frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
)
for row in data:
if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance:
frappe.throw( frappe.throw(
_( _(
"You are picking more than required quantity for {}. Check if there is any other pick list created for {}" f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}."
).format(item_code, item.sales_order) )
) )
frappe.db.set_value(item_table, item.sales_order_item, "picked_qty", already_picked + picked_qty)
@staticmethod
def update_sales_order_picking_status(sales_orders: Set[str]) -> None:
for sales_order in sales_orders:
if sales_order:
frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()
@frappe.whitelist() @frappe.whitelist()
def set_item_locations(self, save=False): def set_item_locations(self, save=False):
@ -309,6 +346,31 @@ class PickList(Document):
return int(flt(min(possible_bundles), precision or 6)) return int(flt(min(possible_bundles), precision or 6))
def get_picked_items_qty(items) -> List[Dict]:
return frappe.db.sql(
f"""
SELECT
sales_order_item,
item_code,
sales_order,
SUM(stock_qty) AS stock_qty,
SUM(picked_qty) AS picked_qty
FROM
`tabPick List Item`
WHERE
sales_order_item IN (
{", ".join(frappe.db.escape(d) for d in items)}
)
AND docstatus = 1
GROUP BY
sales_order_item,
sales_order
FOR UPDATE
""",
as_dict=1,
)
def validate_item_locations(pick_list): def validate_item_locations(pick_list):
if not pick_list.locations: if not pick_list.locations:
frappe.throw(_("Add items in the Item Locations table")) frappe.throw(_("Add items in the Item Locations table"))