Merge branch 'develop' into fg_based_operating_cost
This commit is contained in:
commit
f19212b5c8
3
.github/helper/.flake8_strict
vendored
3
.github/helper/.flake8_strict
vendored
@ -66,7 +66,8 @@ ignore =
|
|||||||
F841,
|
F841,
|
||||||
E713,
|
E713,
|
||||||
E712,
|
E712,
|
||||||
B023
|
B023,
|
||||||
|
B028
|
||||||
|
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
|
if filters.get("based_on") == "Planned Date":
|
||||||
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
|
query_filters["planned_start_date"] = (">=", filters.get("from_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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"),
|
||||||
|
|||||||
@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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,58 +67,96 @@ class PickList(Document):
|
|||||||
),
|
),
|
||||||
title=_("Serial Nos Required"),
|
title=_("Serial Nos Required"),
|
||||||
)
|
)
|
||||||
if len(item.serial_no.split("\n")) == item.picked_qty:
|
|
||||||
continue
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"For item {0} at row {1}, count of serial numbers does not match with the picked quantity"
|
|
||||||
).format(frappe.bold(item.item_code), frappe.bold(item.idx)),
|
|
||||||
title=_("Quantity Mismatch"),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.update_bundle_picked_qty()
|
if len(item.serial_no.split("\n")) != item.picked_qty:
|
||||||
self.update_sales_order_picking_status(update_sales_orders)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
self.update_bundle_picked_qty()
|
|
||||||
self.update_sales_order_picking_status(updated_sales_orders)
|
|
||||||
|
|
||||||
def update_sales_order_item(self, item, picked_qty, item_code):
|
|
||||||
item_table = "Sales Order Item" if not item.product_bundle_item else "Packed Item"
|
|
||||||
stock_qty_field = "stock_qty" if not item.product_bundle_item else "qty"
|
|
||||||
|
|
||||||
already_picked, actual_qty = frappe.db.get_value(
|
|
||||||
item_table,
|
|
||||||
item.sales_order_item,
|
|
||||||
["picked_qty", stock_qty_field],
|
|
||||||
for_update=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.docstatus == 1:
|
|
||||||
if (((already_picked + picked_qty) / actual_qty) * 100) > (
|
|
||||||
100 + flt(frappe.db.get_single_value("Stock Settings", "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 {}"
|
"For item {0} at row {1}, count of serial numbers does not match with the picked quantity"
|
||||||
).format(item_code, item.sales_order)
|
).format(frappe.bold(item.item_code), frappe.bold(item.idx)),
|
||||||
|
title=_("Quantity Mismatch"),
|
||||||
)
|
)
|
||||||
|
|
||||||
frappe.db.set_value(item_table, item.sales_order_item, "picked_qty", already_picked + picked_qty)
|
def on_submit(self):
|
||||||
|
self.update_bundle_picked_qty()
|
||||||
|
self.update_reference_qty()
|
||||||
|
self.update_sales_order_picking_status()
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.update_bundle_picked_qty()
|
||||||
|
self.update_reference_qty()
|
||||||
|
self.update_sales_order_picking_status()
|
||||||
|
|
||||||
|
def update_reference_qty(self):
|
||||||
|
packed_items = []
|
||||||
|
so_items = []
|
||||||
|
|
||||||
|
for item in self.locations:
|
||||||
|
if item.product_bundle_item:
|
||||||
|
packed_items.append(item.sales_order_item)
|
||||||
|
elif item.sales_order_item:
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_sales_order_item_qty(self, so_items):
|
||||||
|
picked_items = get_picked_items_qty(so_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 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)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def update_sales_order_picking_status(sales_orders: Set[str]) -> None:
|
|
||||||
for sales_order in sales_orders:
|
for sales_order in sales_orders:
|
||||||
if sales_order:
|
frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()
|
||||||
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(
|
||||||
|
_(
|
||||||
|
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}."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@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"))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user