Merge branch 'develop' into item-attr-abbr-lowercase
This commit is contained in:
commit
333b2ae721
3
.github/helper/.flake8_strict
vendored
3
.github/helper/.flake8_strict
vendored
@ -66,7 +66,8 @@ ignore =
|
||||
F841,
|
||||
E713,
|
||||
E712,
|
||||
B023
|
||||
B023,
|
||||
B028
|
||||
|
||||
|
||||
max-line-length = 200
|
||||
|
@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"api_details_section",
|
||||
"disabled",
|
||||
"service_provider",
|
||||
"api_endpoint",
|
||||
"url",
|
||||
@ -77,12 +78,18 @@
|
||||
"label": "Service Provider",
|
||||
"options": "frankfurter.app\nexchangerate.host\nCustom",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-10 15:51:14.521174",
|
||||
"modified": "2023-01-09 12:19:03.955906",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
|
@ -137,8 +137,7 @@
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book",
|
||||
"read_only": 1
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname": "2_add_edit_gl_entries",
|
||||
@ -539,7 +538,7 @@
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-28 17:40:01.241908",
|
||||
"modified": "2023-01-17 12:53:53.280620",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
@ -334,7 +334,7 @@ class PaymentReconciliation(Document):
|
||||
)
|
||||
|
||||
# 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"
|
||||
|
||||
journal_account = frappe._dict(
|
||||
@ -471,6 +471,7 @@ class PaymentReconciliation(Document):
|
||||
|
||||
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
|
||||
self.common_filter_conditions.clear()
|
||||
self.accounting_dimension_filter_conditions.clear()
|
||||
self.ple_posting_date_filter.clear()
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
|
@ -49,7 +49,6 @@
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
{{ _("Against") }}: {{ row.against }}
|
||||
<br>{{ _("Remarks") }}: {{ row.remarks }}
|
||||
{% if row.bill_no %}
|
||||
<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }}
|
||||
|
@ -7,7 +7,6 @@ import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils.background_jobs import is_job_queued
|
||||
|
||||
from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry
|
||||
|
||||
@ -27,7 +26,7 @@ def start_payment_ledger_repost(docname=None):
|
||||
"""
|
||||
if docname:
|
||||
repost_doc = frappe.get_doc("Repost Payment Ledger", docname)
|
||||
if repost_doc.docstatus == 1 and repost_doc.repost_status in ["Queued", "Failed"]:
|
||||
if repost_doc.docstatus.is_submitted() and repost_doc.repost_status in ["Queued", "Failed"]:
|
||||
try:
|
||||
for entry in repost_doc.repost_vouchers:
|
||||
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
||||
@ -102,10 +101,9 @@ def execute_repost_payment_ledger(docname):
|
||||
|
||||
job_name = "payment_ledger_repost_" + docname
|
||||
|
||||
if not is_job_queued(job_name):
|
||||
frappe.enqueue(
|
||||
method="erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.start_payment_ledger_repost",
|
||||
docname=docname,
|
||||
is_async=True,
|
||||
job_name=job_name,
|
||||
)
|
||||
frappe.enqueue(
|
||||
method="erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.start_payment_ledger_repost",
|
||||
docname=docname,
|
||||
is_async=True,
|
||||
job_name=job_name,
|
||||
)
|
||||
|
@ -259,9 +259,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
if tax_deducted:
|
||||
net_total = inv.tax_withholding_net_total
|
||||
if ldc:
|
||||
tax_amount = get_tds_amount_from_ldc(
|
||||
ldc, parties, pan_no, tax_details, posting_date, net_total
|
||||
)
|
||||
tax_amount = get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total)
|
||||
else:
|
||||
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
|
||||
|
||||
@ -538,7 +536,7 @@ def get_invoice_total_without_tcs(inv, tax_details):
|
||||
return inv.grand_total - tcs_tax_row_amount
|
||||
|
||||
|
||||
def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
|
||||
def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
|
||||
tds_amount = 0
|
||||
limit_consumed = frappe.db.get_value(
|
||||
"Purchase Invoice",
|
||||
|
@ -25,8 +25,8 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%">{%= __("Date") %}</th>
|
||||
<th style="width: 15%">{%= __("Ref") %}</th>
|
||||
<th style="width: 25%">{%= __("Party") %}</th>
|
||||
<th style="width: 15%">{%= __("Reference") %}</th>
|
||||
<th style="width: 25%">{%= __("Remarks") %}</th>
|
||||
<th style="width: 15%">{%= __("Debit") %}</th>
|
||||
<th style="width: 15%">{%= __("Credit") %}</th>
|
||||
<th style="width: 18%">{%= __("Balance (Dr - Cr)") %}</th>
|
||||
@ -45,7 +45,6 @@
|
||||
<br>
|
||||
{% } %}
|
||||
|
||||
{{ __("Against") }}: {%= data[i].against %}
|
||||
<br>{%= __("Remarks") %}: {%= data[i].remarks %}
|
||||
{% if(data[i].bill_no) { %}
|
||||
<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@ -65,6 +66,12 @@ def get_result(
|
||||
else:
|
||||
total_amount_credited += entry.credit
|
||||
|
||||
## Check if ldc is applied and show rate as per ldc
|
||||
actual_rate = (tds_deducted / total_amount_credited) * 100
|
||||
|
||||
if flt(actual_rate) < flt(rate):
|
||||
rate = actual_rate
|
||||
|
||||
if tds_deducted:
|
||||
row = {
|
||||
"pan"
|
||||
|
@ -439,8 +439,7 @@ def reconcile_against_document(args): # nosemgrep
|
||||
# cancel advance entry
|
||||
doc = frappe.get_doc(voucher_type, voucher_no)
|
||||
frappe.flags.ignore_party_validation = True
|
||||
gl_map = doc.build_gl_map()
|
||||
create_payment_ledger_entry(gl_map, cancel=1, adv_adj=1)
|
||||
_delete_pl_entries(voucher_type, voucher_no)
|
||||
|
||||
for entry in entries:
|
||||
check_if_advance_entry_modified(entry)
|
||||
@ -452,11 +451,23 @@ def reconcile_against_document(args): # nosemgrep
|
||||
else:
|
||||
update_reference_in_payment_entry(entry, doc, do_not_save=True)
|
||||
|
||||
if doc.doctype == "Journal Entry":
|
||||
try:
|
||||
doc.validate_total_debit_and_credit()
|
||||
except Exception as validation_exception:
|
||||
raise frappe.ValidationError(_(f"Validation Error for {doc.name}")) from validation_exception
|
||||
|
||||
doc.save(ignore_permissions=True)
|
||||
# re-submit advance entry
|
||||
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
||||
gl_map = doc.build_gl_map()
|
||||
create_payment_ledger_entry(gl_map, cancel=0, adv_adj=1)
|
||||
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
|
||||
|
||||
# Only update outstanding for newly linked vouchers
|
||||
for entry in entries:
|
||||
update_voucher_outstanding(
|
||||
entry.against_voucher_type, entry.against_voucher, entry.account, entry.party_type, entry.party
|
||||
)
|
||||
|
||||
frappe.flags.ignore_party_validation = False
|
||||
|
||||
|
@ -135,6 +135,10 @@ frappe.ui.form.on('Asset', {
|
||||
}, __("Manage"));
|
||||
}
|
||||
|
||||
if (frm.doc.depr_entry_posting_status === "Failed") {
|
||||
frm.trigger("set_depr_posting_failure_alert");
|
||||
}
|
||||
|
||||
frm.trigger("setup_chart");
|
||||
}
|
||||
|
||||
@ -145,6 +149,19 @@ frappe.ui.form.on('Asset', {
|
||||
}
|
||||
},
|
||||
|
||||
set_depr_posting_failure_alert: function (frm) {
|
||||
const alert = `
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<span class="indicator whitespace-nowrap red">
|
||||
<span>Failed to post depreciation entries</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
frm.dashboard.set_headline_alert(alert);
|
||||
},
|
||||
|
||||
toggle_reference_doc: function(frm) {
|
||||
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
|
||||
frm.set_df_property('purchase_invoice', 'read_only', 1);
|
||||
|
@ -68,6 +68,7 @@
|
||||
"column_break_51",
|
||||
"purchase_receipt_amount",
|
||||
"default_finance_book",
|
||||
"depr_entry_posting_status",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
@ -473,6 +474,16 @@
|
||||
"fieldtype": "Int",
|
||||
"label": "Asset Quantity",
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "depr_entry_posting_status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Depreciation Entry Posting Status",
|
||||
"no_copy": 1,
|
||||
"options": "\nSuccessful\nFailed",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 72,
|
||||
@ -487,7 +498,7 @@
|
||||
{
|
||||
"group": "Repair",
|
||||
"link_doctype": "Asset Repair",
|
||||
"link_fieldname": "asset_name"
|
||||
"link_fieldname": "asset"
|
||||
},
|
||||
{
|
||||
"group": "Value",
|
||||
@ -500,7 +511,7 @@
|
||||
"link_fieldname": "asset"
|
||||
}
|
||||
],
|
||||
"modified": "2022-11-25 12:47:19.689702",
|
||||
"modified": "2023-01-17 00:25:30.387242",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
@ -5,6 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, nowdate, today
|
||||
from frappe.utils.user import get_users_with_role
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_checks_for_pl_and_bs_accounts,
|
||||
@ -18,7 +19,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
||||
)
|
||||
|
||||
|
||||
def post_depreciation_entries(date=None, commit=True):
|
||||
def post_depreciation_entries(date=None):
|
||||
# Return if automatic booking of asset depreciation is disabled
|
||||
if not cint(
|
||||
frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")
|
||||
@ -27,13 +28,24 @@ def post_depreciation_entries(date=None, commit=True):
|
||||
|
||||
if not date:
|
||||
date = today()
|
||||
|
||||
failed_asset_names = []
|
||||
|
||||
for asset_name in get_depreciable_assets(date):
|
||||
asset_doc = frappe.get_doc("Asset", asset_name)
|
||||
|
||||
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
|
||||
|
||||
if commit:
|
||||
try:
|
||||
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
|
||||
frappe.db.commit()
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
failed_asset_names.append(asset_name)
|
||||
|
||||
if failed_asset_names:
|
||||
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
|
||||
notify_depr_entry_posting_error(failed_asset_names)
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def get_depreciable_assets(date):
|
||||
@ -146,6 +158,8 @@ def make_depreciation_entry(asset_depr_schedule_name, date=None):
|
||||
row.value_after_depreciation -= d.depreciation_amount
|
||||
row.db_update()
|
||||
|
||||
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
|
||||
|
||||
asset.set_status()
|
||||
|
||||
return asset_depr_schedule_doc
|
||||
@ -209,6 +223,42 @@ def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation
|
||||
return credit_account, debit_account
|
||||
|
||||
|
||||
def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
|
||||
for asset_name in failed_asset_names:
|
||||
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
|
||||
|
||||
|
||||
def notify_depr_entry_posting_error(failed_asset_names):
|
||||
recipients = get_users_with_role("Accounts Manager")
|
||||
|
||||
if not recipients:
|
||||
recipients = get_users_with_role("System Manager")
|
||||
|
||||
subject = _("Error while posting depreciation entries")
|
||||
|
||||
asset_links = get_comma_separated_asset_links(failed_asset_names)
|
||||
|
||||
message = (
|
||||
_("Hi,")
|
||||
+ "<br>"
|
||||
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
|
||||
+ "."
|
||||
)
|
||||
|
||||
frappe.sendmail(recipients=recipients, subject=subject, message=message)
|
||||
|
||||
|
||||
def get_comma_separated_asset_links(asset_names):
|
||||
asset_links = []
|
||||
|
||||
for asset_name in asset_names:
|
||||
asset_links.append(get_link_to_form("Asset", asset_name))
|
||||
|
||||
asset_links = ", ".join(asset_links)
|
||||
|
||||
return asset_links
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def scrap_asset(asset_name):
|
||||
asset = frappe.get_doc("Asset", asset_name)
|
||||
@ -295,12 +345,12 @@ def reset_depreciation_schedule(asset_doc, date, notes):
|
||||
asset_doc, notes, date_of_return=date
|
||||
)
|
||||
|
||||
modify_depreciation_schedule_for_asset_repairs(asset_doc)
|
||||
modify_depreciation_schedule_for_asset_repairs(asset_doc, notes)
|
||||
|
||||
asset_doc.save()
|
||||
|
||||
|
||||
def modify_depreciation_schedule_for_asset_repairs(asset):
|
||||
def modify_depreciation_schedule_for_asset_repairs(asset, notes):
|
||||
asset_repairs = frappe.get_all(
|
||||
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
|
||||
)
|
||||
@ -309,10 +359,6 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
|
||||
if repair.increase_in_asset_life:
|
||||
asset_repair = frappe.get_doc("Asset Repair", repair.name)
|
||||
asset_repair.modify_depreciation_schedule()
|
||||
notes = _("This schedule was created when Asset {0} went through Asset Repair {1}.").format(
|
||||
get_link_to_form(asset.doctype, asset.name),
|
||||
get_link_to_form(asset_repair.doctype, asset_repair.name),
|
||||
)
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, notes)
|
||||
|
||||
|
||||
|
@ -1549,6 +1549,7 @@ def create_asset(**args):
|
||||
"asset_owner": args.asset_owner or "Company",
|
||||
"is_existing_asset": args.is_existing_asset or 1,
|
||||
"asset_quantity": args.get("asset_quantity") or 1,
|
||||
"depr_entry_posting_status": args.depr_entry_posting_status or "",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -8,7 +8,6 @@ import frappe
|
||||
# import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, get_link_to_form
|
||||
from six import string_types
|
||||
|
||||
import erpnext
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
@ -431,7 +430,7 @@ class AssetCapitalization(StockController):
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was consumed when Asset Capitalization {1} was submitted."
|
||||
"This schedule was created when Asset {0} was consumed through Asset Capitalization {1}."
|
||||
).format(
|
||||
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.get("name"))
|
||||
)
|
||||
@ -522,7 +521,7 @@ class AssetCapitalization(StockController):
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
notes = _(
|
||||
"This schedule was created when target Asset {0} was updated when Asset Capitalization {1} was submitted."
|
||||
"This schedule was created when target Asset {0} was updated through Asset Capitalization {1}."
|
||||
).format(
|
||||
get_link_to_form(asset_doc.doctype, asset_doc.name), get_link_to_form(self.doctype, self.name)
|
||||
)
|
||||
@ -538,7 +537,7 @@ class AssetCapitalization(StockController):
|
||||
if asset.calculate_depreciation:
|
||||
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was restored when Asset Capitalization {1} was cancelled."
|
||||
"This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation."
|
||||
).format(
|
||||
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name)
|
||||
)
|
||||
@ -626,7 +625,7 @@ def get_target_asset_details(asset=None, company=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_consumed_stock_item_details(args):
|
||||
if isinstance(args, string_types):
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
@ -678,7 +677,7 @@ def get_consumed_stock_item_details(args):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_warehouse_details(args):
|
||||
if isinstance(args, string_types):
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
@ -694,7 +693,7 @@ def get_warehouse_details(args):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_consumed_asset_details(args):
|
||||
if isinstance(args, string_types):
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
@ -746,7 +745,7 @@ def get_consumed_asset_details(args):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_service_item_details(args):
|
||||
if isinstance(args, string_types):
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
@ -159,7 +159,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-01-02 15:38:30.766779",
|
||||
"modified": "2023-01-16 21:08:21.421260",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Depreciation Schedule",
|
||||
|
@ -56,8 +56,11 @@ class AssetRepair(AccountsController):
|
||||
):
|
||||
self.modify_depreciation_schedule()
|
||||
|
||||
notes = _("This schedule was created when Asset Repair {0} was submitted.").format(
|
||||
get_link_to_form(self.doctype, self.name)
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was repaired through Asset Repair {1}."
|
||||
).format(
|
||||
get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
|
||||
get_link_to_form(self.doctype, self.name),
|
||||
)
|
||||
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||
@ -80,8 +83,9 @@ class AssetRepair(AccountsController):
|
||||
):
|
||||
self.revert_depreciation_schedule_on_cancellation()
|
||||
|
||||
notes = _("This schedule was created when Asset Repair {0} was cancelled.").format(
|
||||
get_link_to_form(self.doctype, self.name)
|
||||
notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
|
||||
get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
|
||||
get_link_to_form(self.doctype, self.name),
|
||||
)
|
||||
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||
|
@ -127,12 +127,20 @@ class AssetValueAdjustment(Document):
|
||||
current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True
|
||||
current_asset_depr_schedule_doc.cancel()
|
||||
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
|
||||
).format(
|
||||
get_link_to_form(asset.doctype, asset.name),
|
||||
get_link_to_form(self.get("doctype"), self.get("name")),
|
||||
)
|
||||
if self.docstatus == 1:
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
|
||||
).format(
|
||||
get_link_to_form(asset.doctype, asset.name),
|
||||
get_link_to_form(self.get("doctype"), self.get("name")),
|
||||
)
|
||||
elif self.docstatus == 2:
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0}'s Asset Value Adjustment {1} was cancelled."
|
||||
).format(
|
||||
get_link_to_form(asset.doctype, asset.name),
|
||||
get_link_to_form(self.get("doctype"), self.get("name")),
|
||||
)
|
||||
new_asset_depr_schedule_doc.notes = notes
|
||||
|
||||
new_asset_depr_schedule_doc.insert()
|
||||
|
@ -889,6 +889,11 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
self.assertEqual(po.status, "Completed")
|
||||
self.assertEqual(mr.status, "Received")
|
||||
|
||||
def test_variant_item_po(self):
|
||||
po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, po.save)
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
@ -994,8 +999,8 @@ def create_purchase_order(**args):
|
||||
},
|
||||
)
|
||||
|
||||
po.set_missing_values()
|
||||
if not args.do_not_save:
|
||||
po.set_missing_values()
|
||||
po.insert()
|
||||
if not args.do_not_submit:
|
||||
if po.is_subcontracted:
|
||||
|
@ -394,7 +394,7 @@ class AccountsController(TransactionBase):
|
||||
self.get("inter_company_reference")
|
||||
or self.get("inter_company_invoice_reference")
|
||||
or self.get("inter_company_order_reference")
|
||||
):
|
||||
) and not self.get("is_return"):
|
||||
msg = _("Internal Sale or Delivery Reference missing.")
|
||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||
|
@ -37,7 +37,7 @@ def validate_return_against(doc):
|
||||
if (
|
||||
ref_doc.company == doc.company
|
||||
and ref_doc.get(party_type) == doc.get(party_type)
|
||||
and ref_doc.docstatus == 1
|
||||
and ref_doc.docstatus.is_submitted()
|
||||
):
|
||||
# validate posting date time
|
||||
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
|
||||
|
@ -6,6 +6,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
|
||||
|
||||
import erpnext
|
||||
@ -20,7 +21,7 @@ from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
|
||||
|
||||
class calculate_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
def __init__(self, doc: Document):
|
||||
self.doc = doc
|
||||
frappe.flags.round_off_applicable_accounts = []
|
||||
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
|
||||
@ -677,7 +678,7 @@ class calculate_taxes_and_totals(object):
|
||||
)
|
||||
|
||||
def calculate_total_advance(self):
|
||||
if self.doc.docstatus < 2:
|
||||
if not self.doc.docstatus.is_cancelled():
|
||||
total_allocated_amount = sum(
|
||||
flt(adv.allocated_amount, adv.precision("allocated_amount"))
|
||||
for adv in self.doc.get("advances")
|
||||
@ -708,7 +709,7 @@ class calculate_taxes_and_totals(object):
|
||||
)
|
||||
)
|
||||
|
||||
if self.doc.docstatus == 0:
|
||||
if self.doc.docstatus.is_draft():
|
||||
if self.doc.get("write_off_outstanding_amount_automatically"):
|
||||
self.doc.write_off_amount = 0
|
||||
|
||||
|
@ -13,38 +13,24 @@ frappe.query_reports["Work Order Summary"] = {
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "fiscal_year",
|
||||
label: __("Fiscal Year"),
|
||||
fieldtype: "Link",
|
||||
options: "Fiscal Year",
|
||||
default: frappe.defaults.get_user_default("fiscal_year"),
|
||||
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: __("Based On"),
|
||||
fieldname:"based_on",
|
||||
fieldtype: "Select",
|
||||
options: "Creation Date\nPlanned Date\nActual Date",
|
||||
default: "Creation Date"
|
||||
},
|
||||
{
|
||||
label: __("From Posting Date"),
|
||||
fieldname:"from_date",
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: frappe.datetime.add_months(frappe.datetime.get_today(), -3),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("To Posting Date"),
|
||||
fieldname:"to_date",
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
@ -31,6 +31,7 @@ def get_data(filters):
|
||||
"sales_order",
|
||||
"production_item",
|
||||
"qty",
|
||||
"creation",
|
||||
"produced_qty",
|
||||
"planned_start_date",
|
||||
"planned_end_date",
|
||||
@ -47,11 +48,17 @@ def get_data(filters):
|
||||
if filters.get(field):
|
||||
query_filters[field] = filters.get(field)
|
||||
|
||||
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
|
||||
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
|
||||
if filters.get("based_on") == "Planned 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(
|
||||
"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 = []
|
||||
@ -213,6 +220,12 @@ def get_columns(filters):
|
||||
"options": "Sales Order",
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Created On"),
|
||||
"fieldname": "creation",
|
||||
"fieldtype": "Date",
|
||||
"width": 150,
|
||||
},
|
||||
{
|
||||
"label": _("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.add_naming_series_to_old_projects # 1-02-2021
|
||||
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.update_vehicle_no_reqd_condition
|
||||
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
|
||||
@ -269,6 +268,7 @@ erpnext.patches.v13_0.reset_corrupt_defaults
|
||||
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
|
||||
erpnext.patches.v15_0.delete_taxjar_doctypes
|
||||
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
|
||||
erpnext.patches.v14_0.update_reference_due_date_in_journal_entry
|
||||
|
||||
[post_model_sync]
|
||||
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
||||
@ -291,6 +291,7 @@ erpnext.patches.v13_0.update_exchange_rate_settings
|
||||
erpnext.patches.v14_0.delete_amazon_mws_doctype
|
||||
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.item_reposting_for_incorrect_sl_and_gl
|
||||
erpnext.patches.v14_0.update_batch_valuation_flag
|
||||
erpnext.patches.v14_0.delete_non_profit_doctypes
|
||||
erpnext.patches.v13_0.add_cost_center_in_loans
|
||||
|
@ -7,6 +7,7 @@ from erpnext.stock.stock_ledger import update_entries_after
|
||||
|
||||
def execute():
|
||||
doctypes_to_reload = [
|
||||
("setup", "company"),
|
||||
("stock", "repost_item_valuation"),
|
||||
("stock", "stock_entry_detail"),
|
||||
("stock", "purchase_receipt_item"),
|
||||
|
@ -0,0 +1,12 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.get_value("Journal Entry Account", {"reference_due_date": ""}):
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE `tabJournal Entry Account`
|
||||
SET reference_due_date = NULL
|
||||
WHERE reference_due_date = ''
|
||||
"""
|
||||
)
|
@ -80,7 +80,7 @@ class Task(NestedSet):
|
||||
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled."
|
||||
"Cannot complete task {0} as its dependant task {1} are not completed / cancelled."
|
||||
).format(frappe.bold(self.name), frappe.bold(d.task))
|
||||
)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
frappe.provide("erpnext.accounts.bank_reconciliation");
|
||||
|
||||
erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
||||
constructor(company, bank_account) {
|
||||
constructor(company, bank_account, bank_statement_from_date, bank_statement_to_date, filter_by_reference_date, from_reference_date, to_reference_date) {
|
||||
this.bank_account = bank_account;
|
||||
this.company = company;
|
||||
this.make_dialog();
|
||||
|
@ -194,14 +194,7 @@ def get_list_context(context=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_order(source_name, target_doc=None):
|
||||
quotation = frappe.db.get_value(
|
||||
"Quotation", source_name, ["transaction_date", "valid_till"], as_dict=1
|
||||
)
|
||||
if quotation.valid_till and (
|
||||
quotation.valid_till < quotation.transaction_date or quotation.valid_till < getdate(nowdate())
|
||||
):
|
||||
frappe.throw(_("Validity period of this quotation has ended."))
|
||||
def make_sales_order(source_name: str, target_doc=None):
|
||||
return _make_sales_order(source_name, target_doc)
|
||||
|
||||
|
||||
|
@ -136,17 +136,20 @@ class TestQuotation(FrappeTestCase):
|
||||
sales_order.payment_schedule[1].due_date, getdate(add_days(quotation.transaction_date, 30))
|
||||
)
|
||||
|
||||
def test_valid_till(self):
|
||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||
|
||||
def test_valid_till_before_transaction_date(self):
|
||||
quotation = frappe.copy_doc(test_records[0])
|
||||
quotation.valid_till = add_days(quotation.transaction_date, -1)
|
||||
self.assertRaises(frappe.ValidationError, quotation.validate)
|
||||
|
||||
def test_so_from_expired_quotation(self):
|
||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||
|
||||
quotation = frappe.copy_doc(test_records[0])
|
||||
quotation.valid_till = add_days(nowdate(), -1)
|
||||
quotation.insert()
|
||||
quotation.submit()
|
||||
self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
|
||||
|
||||
make_sales_order(quotation.name)
|
||||
|
||||
def test_shopping_cart_without_website_item(self):
|
||||
if frappe.db.exists("Website Item", {"item_code": "_Test Item Home Desktop 100"}):
|
||||
|
@ -208,7 +208,7 @@ class SalesOrder(SellingController):
|
||||
for quotation in set(d.prevdoc_docname for d in self.get("items")):
|
||||
if quotation:
|
||||
doc = frappe.get_doc("Quotation", quotation)
|
||||
if doc.docstatus == 2:
|
||||
if doc.docstatus.is_cancelled():
|
||||
frappe.throw(_("Quotation {0} is cancelled").format(quotation))
|
||||
|
||||
doc.set_status(update=True)
|
||||
|
@ -14,7 +14,6 @@ def get_data():
|
||||
},
|
||||
"internal_links": {
|
||||
"Quotation": ["items", "prevdoc_docname"],
|
||||
"Material Request": ["items", "material_request"],
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
|
@ -638,7 +638,6 @@
|
||||
"width": "70px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "ordered_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Ordered Qty",
|
||||
@ -865,7 +864,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-01-12 13:13:28.691585",
|
||||
"modified": "2022-12-25 02:51:10.247569",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order Item",
|
||||
|
@ -81,6 +81,11 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No
|
||||
if entries:
|
||||
return flt(entries[0].exchange_rate)
|
||||
|
||||
if frappe.get_cached_value(
|
||||
"Currency Exchange Settings", "Currency Exchange Settings", "disabled"
|
||||
):
|
||||
return 0.00
|
||||
|
||||
try:
|
||||
cache = frappe.cache()
|
||||
key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency)
|
||||
|
@ -106,7 +106,6 @@ class TestItem(FrappeTestCase):
|
||||
"conversion_factor": 1.0,
|
||||
"reserved_qty": 1,
|
||||
"actual_qty": 5,
|
||||
"ordered_qty": 10,
|
||||
"projected_qty": 14,
|
||||
}
|
||||
|
||||
|
@ -4,14 +4,14 @@
|
||||
import json
|
||||
from collections import OrderedDict, defaultdict
|
||||
from itertools import groupby
|
||||
from typing import Dict, List, Set
|
||||
from typing import Dict, List
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_child_doc
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.functions import Locate
|
||||
from frappe.query_builder.functions import IfNull, Locate, Sum
|
||||
from frappe.utils import cint, floor, flt, today
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
|
||||
@ -41,7 +41,9 @@ class PickList(Document):
|
||||
)
|
||||
|
||||
def before_submit(self):
|
||||
update_sales_orders = set()
|
||||
self.validate_picked_items()
|
||||
|
||||
def validate_picked_items(self):
|
||||
for item in self.locations:
|
||||
if self.scan_mode and item.picked_qty < item.stock_qty:
|
||||
frappe.throw(
|
||||
@ -50,17 +52,14 @@ class PickList(Document):
|
||||
).format(item.idx, item.stock_qty - item.picked_qty, item.stock_uom),
|
||||
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
|
||||
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"):
|
||||
continue
|
||||
|
||||
if not item.serial_no:
|
||||
frappe.throw(
|
||||
_("Row #{0}: {1} does not have any available serial numbers in {2}").format(
|
||||
@ -68,58 +67,96 @@ class PickList(Document):
|
||||
),
|
||||
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()
|
||||
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"))
|
||||
):
|
||||
if len(item.serial_no.split("\n")) != item.picked_qty:
|
||||
frappe.throw(
|
||||
_(
|
||||
"You are picking more than required quantity for {}. Check if there is any other pick list created for {}"
|
||||
).format(item_code, item.sales_order)
|
||||
"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"),
|
||||
)
|
||||
|
||||
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:
|
||||
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()
|
||||
def set_item_locations(self, save=False):
|
||||
@ -309,6 +346,31 @@ class PickList(Document):
|
||||
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):
|
||||
if not pick_list.locations:
|
||||
frappe.throw(_("Add items in the Item Locations table"))
|
||||
@ -441,42 +503,30 @@ def get_available_item_locations_for_serialized_item(
|
||||
def get_available_item_locations_for_batched_item(
|
||||
item_code, from_warehouses, required_qty, company
|
||||
):
|
||||
warehouse_condition = "and warehouse in %(warehouses)s" if from_warehouses else ""
|
||||
batch_locations = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
sle.`warehouse`,
|
||||
sle.`batch_no`,
|
||||
SUM(sle.`actual_qty`) AS `qty`
|
||||
FROM
|
||||
`tabStock Ledger Entry` sle, `tabBatch` batch
|
||||
WHERE
|
||||
sle.batch_no = batch.name
|
||||
and sle.`item_code`=%(item_code)s
|
||||
and sle.`company` = %(company)s
|
||||
and batch.disabled = 0
|
||||
and sle.is_cancelled=0
|
||||
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
|
||||
{warehouse_condition}
|
||||
GROUP BY
|
||||
sle.`warehouse`,
|
||||
sle.`batch_no`,
|
||||
sle.`item_code`
|
||||
HAVING `qty` > 0
|
||||
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`, sle.`batch_no`, sle.`warehouse`
|
||||
""".format(
|
||||
warehouse_condition=warehouse_condition
|
||||
),
|
||||
{ # nosec
|
||||
"item_code": item_code,
|
||||
"company": company,
|
||||
"today": today(),
|
||||
"warehouses": from_warehouses,
|
||||
},
|
||||
as_dict=1,
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
batch = frappe.qb.DocType("Batch")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(sle)
|
||||
.from_(batch)
|
||||
.select(sle.warehouse, sle.batch_no, Sum(sle.actual_qty).as_("qty"))
|
||||
.where(
|
||||
(sle.batch_no == batch.name)
|
||||
& (sle.item_code == item_code)
|
||||
& (sle.company == company)
|
||||
& (batch.disabled == 0)
|
||||
& (sle.is_cancelled == 0)
|
||||
& (IfNull(batch.expiry_date, "2200-01-01") > today())
|
||||
)
|
||||
.groupby(sle.warehouse, sle.batch_no, sle.item_code)
|
||||
.having(Sum(sle.actual_qty) > 0)
|
||||
.orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse)
|
||||
)
|
||||
|
||||
return batch_locations
|
||||
if from_warehouses:
|
||||
query = query.where(sle.warehouse.isin(from_warehouses))
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
|
||||
def get_available_item_locations_for_serial_and_batched_item(
|
||||
|
@ -1,7 +1,10 @@
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "pick_list",
|
||||
"internal_links": {
|
||||
"Sales Order": ["locations", "sales_order"],
|
||||
},
|
||||
"transactions": [
|
||||
{"items": ["Stock Entry", "Delivery Note"]},
|
||||
{"items": ["Stock Entry", "Sales Order", "Delivery Note"]},
|
||||
],
|
||||
}
|
||||
|
@ -236,8 +236,10 @@ def validate_item_details(args, item):
|
||||
|
||||
validate_end_of_life(item.name, item.end_of_life, item.disabled)
|
||||
|
||||
if args.transaction_type == "selling" and cint(item.has_variants):
|
||||
throw(_("Item {0} is a template, please select one of its variants").format(item.name))
|
||||
if cint(item.has_variants):
|
||||
msg = f"Item {item.name} is a template, please select one of its variants"
|
||||
|
||||
throw(_(msg), title=_("Template Item Selected"))
|
||||
|
||||
elif args.transaction_type == "buying" and args.doctype != "Material Request":
|
||||
if args.get("is_subcontracted"):
|
||||
@ -1181,7 +1183,7 @@ def get_projected_qty(item_code, warehouse):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bin_details(item_code, warehouse, company=None, include_child_warehouses=False):
|
||||
bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0, "ordered_qty": 0}
|
||||
bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
|
||||
|
||||
if warehouse:
|
||||
from frappe.query_builder.functions import Coalesce, Sum
|
||||
@ -1197,7 +1199,6 @@ def get_bin_details(item_code, warehouse, company=None, include_child_warehouses
|
||||
Coalesce(Sum(bin.projected_qty), 0).as_("projected_qty"),
|
||||
Coalesce(Sum(bin.actual_qty), 0).as_("actual_qty"),
|
||||
Coalesce(Sum(bin.reserved_qty), 0).as_("reserved_qty"),
|
||||
Coalesce(Sum(bin.ordered_qty), 0).as_("ordered_qty"),
|
||||
)
|
||||
.where((bin.item_code == item_code) & (bin.warehouse.isin(warehouses)))
|
||||
).run(as_dict=True)[0]
|
||||
|
@ -262,15 +262,17 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
def get_gl_entries(self, warehouse_account=None):
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
|
||||
if not erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
return []
|
||||
|
||||
gl_entries = []
|
||||
self.make_item_gl_entries(gl_entries, warehouse_account)
|
||||
|
||||
return process_gl_map(gl_entries)
|
||||
|
||||
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
|
||||
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
|
||||
warehouse_with_no_account = []
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user