Merge remote-tracking branch 'upstream/version-14-hotfix' into mergify/bp/version-14-hotfix/pr-41384

This commit is contained in:
barredterra
2024-06-20 13:47:58 +02:00
105 changed files with 919 additions and 458 deletions

View File

@@ -69,12 +69,14 @@ repos:
rev: v0.2.0
hooks:
- id: ruff
name: "Run ruff linter and apply fixes"
args: ["--fix"]
name: "Run ruff import sorter"
args: ["--select=I", "--fix"]
- id: ruff
name: "Run ruff linter"
- id: ruff-format
name: "Format Python code"
name: "Run ruff formatter"
ci:
autoupdate_schedule: weekly

View File

@@ -82,7 +82,7 @@
"icon": "fa fa-calendar",
"idx": 1,
"links": [],
"modified": "2020-11-05 12:16:53.081573",
"modified": "2024-05-27 17:29:55.560840",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year",
@@ -126,6 +126,10 @@
{
"read": 1,
"role": "Stock Manager"
},
{
"read": 1,
"role": "Auditor"
}
],
"show_name_in_global_search": 1,

View File

@@ -171,7 +171,7 @@ frappe.ui.form.on("Journal Entry", {
!(frm.doc.accounts || []).length ||
((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)
) {
if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) {
if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) {
return frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
@@ -283,7 +283,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
filters: [[jvd.reference_type, "docstatus", "=", 1]],
};
if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) {
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
// Filter by cost center
if (jvd.cost_center) {
@@ -295,7 +295,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
}
if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) {
// party_type and party mandatory
frappe.model.validate_missing(jvd, "party_type");
frappe.model.validate_missing(jvd, "party");

View File

@@ -21,7 +21,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_query("paid_from", function() {
frm.events.validate_company(frm);
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type) ?
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
return {
filters: {
@@ -75,7 +75,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_query("paid_to", function() {
frm.events.validate_company(frm);
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type) ?
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
return {
filters: {
@@ -121,7 +121,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
if (['Purchase Invoice', 'Sales Invoice'].includes(child.reference_doctype) && child.reference_name) {
return {
query: "erpnext.controllers.queries.get_payment_terms_for_references",
filters: {
@@ -485,7 +485,7 @@ frappe.ui.form.on('Payment Entry', {
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from){
if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
@@ -853,7 +853,7 @@ frappe.ui.form.on('Payment Entry', {
}
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"))
if(paid_amount > total_negative_outstanding) {
if(total_negative_outstanding == 0) {
@@ -988,7 +988,7 @@ frappe.ui.form.on('Payment Entry', {
}
if(frm.doc.party_type=="Customer" &&
!in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype)
!["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"].includes(row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice, Journal Entry or Dunning", [row.idx]));
@@ -996,7 +996,7 @@ frappe.ui.form.on('Payment Entry', {
}
if(frm.doc.party_type=="Supplier" &&
!in_list(["Purchase Order", "Purchase Invoice", "Journal Entry"], row.reference_doctype)
!["Purchase Order", "Purchase Invoice", "Journal Entry"].includes(row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry", [row.idx]));
@@ -1080,7 +1080,7 @@ frappe.ui.form.on('Payment Entry', {
bank_account: function(frm) {
const field = frm.doc.payment_type == "Pay" ? "paid_from":"paid_to";
if (frm.doc.bank_account && in_list(['Pay', 'Receive'], frm.doc.payment_type)) {
if (frm.doc.bank_account && ['Pay', 'Receive'].includes(frm.doc.payment_type)) {
frappe.call({
method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details",
args: {
@@ -1395,8 +1395,9 @@ frappe.ui.form.on('Payment Entry Reference', {
args: {
reference_doctype: row.reference_doctype,
reference_name: row.reference_name,
party_account_currency: frm.doc.payment_type=="Receive" ?
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency
party_account_currency: frm.doc.payment_type == "Receive" ? frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency,
party_type: frm.doc.party_type,
party: frm.doc.party,
},
callback: function(r, rt) {
if(r.message) {

View File

@@ -9,8 +9,7 @@ import frappe
from frappe import ValidationError, _, qb, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
from frappe.utils.data import comma_and, fmt_money
from pypika import Case
from pypika.functions import Coalesce, Sum
from pypika.functions import Sum
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
@@ -348,7 +347,11 @@ class PaymentEntry(AccountsController):
continue
ref_details = get_reference_details(
d.reference_doctype, d.reference_name, self.party_account_currency
d.reference_doctype,
d.reference_name,
self.party_account_currency,
self.party_type,
self.party,
)
# Only update exchange rate when the reference is Journal Entry
@@ -1883,33 +1886,42 @@ def get_company_defaults(company):
return frappe.get_cached_value("Company", company, fields, as_dict=1)
def get_outstanding_on_journal_entry(name):
gl = frappe.qb.DocType("GL Entry")
res = (
frappe.qb.from_(gl)
.select(
Case()
.when(
gl.party_type == "Customer",
Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0),
)
.else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0))
.as_("outstanding_amount")
)
def get_outstanding_on_journal_entry(voucher_no, party_type, party):
ple = frappe.qb.DocType("Payment Ledger Entry")
outstanding = (
frappe.qb.from_(ple)
.select(Sum(ple.amount_in_account_currency))
.where(
(Coalesce(gl.party_type, "") != "")
& (gl.is_cancelled == 0)
& ((gl.voucher_no == name) | (gl.against_voucher == name))
(ple.against_voucher_no == voucher_no)
& (ple.party_type == party_type)
& (ple.party == party)
& (ple.delinked == 0)
)
).run(as_dict=True)
).run()
outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
outstanding_amount = outstanding[0][0] if outstanding else 0
return outstanding_amount
total = (
frappe.qb.from_(ple)
.select(Sum(ple.amount_in_account_currency))
.where(
(ple.voucher_no == voucher_no)
& (ple.party_type == party_type)
& (ple.party == party)
& (ple.delinked == 0)
)
).run()
total_amount = total[0][0] if total else 0
return outstanding_amount, total_amount
@frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency):
def get_reference_details(
reference_doctype, reference_name, party_account_currency, party_type=None, party=None
):
total_amount = outstanding_amount = exchange_rate = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
@@ -1920,12 +1932,13 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
exchange_rate = 1
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
outstanding_amount, total_amount = get_outstanding_on_journal_entry(
reference_name, party_type, party
)
elif reference_doctype != "Journal Entry":
if not total_amount:

View File

@@ -1087,7 +1087,9 @@ class TestPaymentEntry(FrappeTestCase):
pe.source_exchange_rate = 50
pe.save()
ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
ref_details = get_reference_details(
so.doctype, so.name, pe.paid_from_account_currency, "Customer", so.customer
)
expected_response = {
"total_amount": 5000.0,
"outstanding_amount": 5000.0,

View File

@@ -28,7 +28,7 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) {
if (
frm.doc.payment_request_type == "Inward" &&
frm.doc.payment_channel !== "Phone" &&
!in_list(["Initiated", "Paid"], frm.doc.status) &&
!["Initiated", "Paid"].includes(frm.doc.status) &&
!frm.doc.__islocal &&
frm.doc.docstatus == 1
) {

View File

@@ -596,7 +596,11 @@ def update_payment_req_status(doc, method):
if payment_request_name:
ref_details = get_reference_details(
ref.reference_doctype, ref.reference_name, doc.party_account_currency
ref.reference_doctype,
ref.reference_name,
doc.party_account_currency,
doc.party_type,
doc.party,
)
pay_req_doc = frappe.get_doc("Payment Request", payment_request_name)
status = pay_req_doc.status

View File

@@ -33,7 +33,7 @@ class POSClosingEntry(StatusUpdater):
for key, value in pos_occurences.items():
if len(value) > 1:
error_list.append(
_(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}")
_("{0} is added multiple times on rows: {1}").format(frappe.bold(key), frappe.bold(value))
)
if error_list:

View File

@@ -29,7 +29,7 @@ class POSInvoiceMergeLog(Document):
for key, value in pos_occurences.items():
if len(value) > 1:
error_list.append(
_(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}")
_("{0} is added multiple times on rows: {1}").format(frappe.bold(key), frappe.bold(value))
)
if error_list:
@@ -441,7 +441,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
if closing_entry:
closing_entry.set_status(update=True, status="Failed")
if isinstance(error_message, list):
error_message = frappe.json.dumps(error_message)
error_message = json.dumps(error_message)
closing_entry.db_set("error_message", error_message)
raise

View File

@@ -74,15 +74,21 @@
"discount_amount",
"discount_percentage",
"for_price_list",
"section_break_13",
"threshold_percentage",
"priority",
"dynamic_condition_tab",
"condition",
"column_break_66",
"section_break_13",
"apply_multiple_pricing_rules",
"apply_discount_on_rate",
"column_break_66",
"threshold_percentage",
"validate_pricing_rule_section",
"validate_applied_rule",
"column_break_texp",
"rule_description",
"priority_section",
"has_priority",
"column_break_sayg",
"priority",
"help_section",
"pricing_rule_help",
"reference_section",
@@ -477,7 +483,7 @@
{
"collapsible": 1,
"fieldname": "section_break_13",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Advanced Settings"
},
{
@@ -487,6 +493,7 @@
"label": "Threshold for Suggestion (In Percentage)"
},
{
"depends_on": "has_priority",
"description": "Higher the number, higher the priority",
"fieldname": "priority",
"fieldtype": "Select",
@@ -513,6 +520,7 @@
{
"default": "0",
"depends_on": "eval:doc.price_or_product_discount == 'Price'",
"description": "If enabled, then system will only validate the pricing rule and not apply automatically. User has to manually set the discount percentage / margin / free items to validate the pricing rule",
"fieldname": "validate_applied_rule",
"fieldtype": "Check",
"label": "Validate Applied Rule"
@@ -525,7 +533,8 @@
},
{
"fieldname": "help_section",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Help Article",
"options": "Simple"
},
{
@@ -603,12 +612,42 @@
"fieldname": "apply_recursion_over",
"fieldtype": "Float",
"label": "Apply Recursion Over (As Per Transaction UOM)"
},
{
"fieldname": "priority_section",
"fieldtype": "Section Break",
"label": "Priority"
},
{
"fieldname": "dynamic_condition_tab",
"fieldtype": "Tab Break",
"label": "Dynamic Condition"
},
{
"fieldname": "validate_pricing_rule_section",
"fieldtype": "Section Break",
"label": "Validate Pricing Rule"
},
{
"fieldname": "column_break_texp",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_sayg",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Enable this checkbox even if you want to set the zero priority",
"fieldname": "has_priority",
"fieldtype": "Check",
"label": "Has Priority"
}
],
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2023-02-14 04:53:34.887358",
"modified": "2024-05-17 13:16:34.496704",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -47,6 +47,12 @@ class PricingRule(Document):
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
def validate_mandatory(self):
if self.has_priority and not self.priority:
throw(_("Priority is mandatory"), frappe.MandatoryError, _("Please Set Priority"))
if self.priority and not self.has_priority:
self.has_priority = 1
for apply_on, field in apply_on_dict.items():
if self.apply_on == apply_on and len(self.get(field) or []) < 1:
throw(_("{0} is not added in the table").format(apply_on), frappe.MandatoryError)

View File

@@ -1031,6 +1031,62 @@ class TestPricingRule(unittest.TestCase):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def test_priority_of_multiple_pricing_rules(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 1",
"name": "_Test Pricing Rule 1",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 10,
"has_priority": 1,
"priority": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 2",
"name": "_Test Pricing Rule 2",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 20,
"has_priority": 1,
"priority": 3,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
self.assertEqual(so.items[0].discount_percentage, 20)
self.assertEqual(so.items[0].rate, 800)
frappe.delete_doc_if_exists("Sales Order", so.name)
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_dependencies = ["Campaign"]
@@ -1059,6 +1115,7 @@ def make_pricing_rule(**args):
"priority": args.priority or 1,
"discount_amount": args.discount_amount or 0.0,
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
"has_priority": args.has_priority or 0,
}
)

View File

@@ -33,6 +33,9 @@ def get_pricing_rules(args, doc=None):
for apply_on in ["Item Code", "Item Group", "Brand"]:
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
if pricing_rules and pricing_rules[0].has_priority:
continue
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
break

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
from frappe import _, qb
from frappe.model.document import Document
@@ -479,7 +481,7 @@ def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
running_doc = None
if for_filter:
if isinstance(for_filter, str):
for_filter = frappe.json.loads(for_filter)
for_filter = json.loads(for_filter)
running_doc = frappe.db.get_value(
"Process Payment Reconciliation",

View File

@@ -104,7 +104,7 @@ def set_ageing(doc, entry):
ageing_filters = frappe._dict(
{
"company": doc.company,
"report_date": doc.to_date,
"report_date": doc.posting_date,
"ageing_based_on": doc.ageing_based_on,
"range1": 30,
"range2": 60,

View File

@@ -340,10 +340,11 @@
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 25%">30 Days</th>
<th style="width: 25%">60 Days</th>
<th style="width: 25%">90 Days</th>
<th style="width: 25%">120 Days</th>
<th style="width: 25%">0 - 30 Days</th>
<th style="width: 25%">30 - 60 Days</th>
<th style="width: 25%">60 - 90 Days</th>
<th style="width: 25%">90 - 120 Days</th>
<th style="width: 20%">Above 120 Days</th>
</tr>
</thead>
<tbody>
@@ -352,6 +353,7 @@
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
</tr>
</tbody>
</table>

View File

@@ -440,8 +440,12 @@ function hide_fields(doc) {
var item_fields_stock = ['warehouse_section', 'received_qty', 'rejected_qty'];
cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock,
(cint(doc.update_stock)==1 || cint(doc.is_return)==1 ? true : false));
if (cur_frm.fields_dict["items"]) {
cur_frm.fields_dict["items"].grid.set_column_disp(
item_fields_stock,
cint(doc.update_stock) == 1 || cint(doc.is_return) == 1 ? true : false
);
}
cur_frm.refresh_fields();
}

View File

@@ -284,7 +284,7 @@ class PurchaseInvoice(BuyingController):
stock_not_billed_account = self.get_company_default("stock_received_but_not_billed")
stock_items = self.get_stock_items()
asset_received_but_not_billed = None
self.asset_received_but_not_billed = None
if self.update_stock:
self.validate_item_code()
@@ -367,26 +367,45 @@ class PurchaseInvoice(BuyingController):
frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and item.pr_detail:
if not asset_received_but_not_billed:
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
item.expense_account = asset_received_but_not_billed
elif item.is_fixed_asset:
account_type = (
"capital_work_in_progress_account"
if is_cwip_accounting_enabled(item.asset_category)
else "fixed_asset_account"
)
asset_category_account = get_asset_category_account(
account_type, item=item.item_code, company=self.company
)
if not asset_category_account:
form_link = get_link_to_form("Asset Category", item.asset_category)
throw(
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
title=_("Missing Account"),
account = None
if item.pr_detail:
if not self.asset_received_but_not_billed:
self.asset_received_but_not_billed = self.get_company_default(
"asset_received_but_not_billed"
)
# check if 'Asset Received But Not Billed' account is credited in Purchase receipt or not
arbnb_booked_in_pr = frappe.db.get_value(
"GL Entry",
{
"voucher_type": "Purchase Receipt",
"voucher_no": item.purchase_receipt,
"account": self.asset_received_but_not_billed,
},
"name",
)
item.expense_account = asset_category_account
if arbnb_booked_in_pr:
account = self.asset_received_but_not_billed
if not account:
account_type = (
"capital_work_in_progress_account"
if is_cwip_accounting_enabled(item.asset_category)
else "fixed_asset_account"
)
account = get_asset_category_account(
account_type, item=item.item_code, company=self.company
)
if not account:
form_link = get_link_to_form("Asset Category", item.asset_category)
throw(
_("Please set Fixed Asset Account in {} against {}.").format(
form_link, self.company
),
title=_("Missing Account"),
)
item.expense_account = account
elif not item.expense_account and for_validate:
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
@@ -982,7 +1001,7 @@ class PurchaseInvoice(BuyingController):
pr_items = frappe.get_all(
"Purchase Receipt Item",
filters={"parent": ("in", linked_purchase_receipts)},
fields=["name", "provisional_expense_account", "qty", "base_rate"],
fields=["name", "provisional_expense_account", "qty", "base_rate", "rate"],
)
default_provisional_account = self.get_company_default("default_provisional_account")
provisional_accounts = set(
@@ -1010,6 +1029,7 @@ class PurchaseInvoice(BuyingController):
"provisional_account": item.provisional_expense_account or default_provisional_account,
"qty": item.qty,
"base_rate": item.base_rate,
"rate": item.rate,
"has_provisional_entry": item.name in rows_with_provisional_entries,
}
@@ -1026,7 +1046,10 @@ class PurchaseInvoice(BuyingController):
self.posting_date,
pr_item.get("provisional_account"),
reverse=1,
item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")),
item_amount=(
(min(item.qty, pr_item.get("qty")) * pr_item.get("rate"))
* purchase_receipt_doc.get("conversion_rate")
),
)
def update_gross_purchase_amount_for_linked_assets(self, item):

View File

@@ -1,7 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:ACC-REPOST-{#####}",
"creation": "2023-07-04 13:07:32.923675",
"default_view": "List",
"doctype": "DocType",
@@ -55,14 +53,15 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-09-26 14:21:27.362567",
"modified": "2024-06-03 17:30:37.012593",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -71,7 +70,9 @@
"read": 1,
"report": 1,
"role": "System Manager",
"select": 1,
"share": 1,
"submit": 1,
"write": 1
}
],

View File

@@ -17,7 +17,7 @@
"in_create": 1,
"issingle": 1,
"links": [],
"modified": "2023-11-07 14:24:13.321522",
"modified": "2024-06-06 13:56:37.908879",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger Settings",
@@ -30,13 +30,17 @@
"print": 1,
"read": 1,
"role": "Administrator",
"select": 1,
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"read": 1,
"role": "System Manager",
"select": 1
"select": 1,
"write": 1
}
],
"sort_field": "modified",

View File

@@ -1,6 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-10-19 21:59:33.553852",
"doctype": "DocType",
"editable_grid": 1,
@@ -99,13 +98,15 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-09-26 14:21:35.719727",
"modified": "2024-06-03 17:31:04.472279",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -114,7 +115,9 @@
"read": 1,
"report": 1,
"role": "System Manager",
"select": 1,
"share": 1,
"submit": 1,
"write": 1
},
{

View File

@@ -200,7 +200,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
if(cur_frm.meta._default_print_format) {
cur_frm.meta.default_print_format = cur_frm.meta._default_print_format;
cur_frm.meta._default_print_format = null;
} else if(in_list([cur_frm.pos_print_format, cur_frm.return_print_format], cur_frm.meta.default_print_format)) {
} else if([cur_frm.pos_print_format, cur_frm.return_print_format].includes(cur_frm.meta.default_print_format)) {
cur_frm.meta.default_print_format = null;
cur_frm.meta._default_print_format = null;
}

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import copy
import json
import frappe
from frappe.model.dynamic_links import get_dynamic_link_map
@@ -2978,10 +2979,8 @@ class TestSalesInvoice(FrappeTestCase):
["2021-06-30", 20000.0, 21366.12, True],
["2022-06-30", 20000.0, 41366.12, False],
["2023-06-30", 20000.0, 61366.12, False],
["2024-06-30", 20000.0, 81366.12, False],
["2025-06-06", 18633.88, 100000.0, False],
["2024-06-06", 38633.88, 100000.0, False],
]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
@@ -3479,9 +3478,9 @@ class TestSalesInvoice(FrappeTestCase):
map_docs(
method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
source_names=frappe.json.dumps([dn1.name, dn2.name]),
source_names=json.dumps([dn1.name, dn2.name]),
target_doc=si,
args=frappe.json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}),
args=json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}),
)
si.save().submit()

View File

@@ -867,7 +867,8 @@
"label": "Purchase Order",
"options": "Purchase Order",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "column_break_92",
@@ -892,7 +893,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-11-14 18:34:10.479329",
"modified": "2024-05-23 16:36:18.970862",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
from frappe import _, qb
from frappe.model.document import Document
@@ -142,7 +144,7 @@ def get_linked_payments_for_doc(
@frappe.whitelist()
def create_unreconcile_doc_for_selection(selections=None):
if selections:
selections = frappe.json.loads(selections)
selections = json.loads(selections)
# assuming each row is a unique voucher
for row in selections:
unrecon = frappe.new_doc("Unreconcile Payment")

View File

@@ -49,7 +49,6 @@ def get_conditions(filters):
if filters.account_type:
conditions["account_type"] = filters.account_type
return conditions
if filters.company:
conditions["company"] = filters.company

View File

@@ -1028,20 +1028,6 @@ class ReceivablePayableReport:
fieldtype="Link",
options="Contact",
)
if self.filters.party_type == "Customer":
self.add_column(
_("Customer Name"),
fieldname="customer_name",
fieldtype="Link",
options="Customer",
)
elif self.filters.party_type == "Supplier":
self.add_column(
_("Supplier Name"),
fieldname="supplier_name",
fieldtype="Link",
options="Supplier",
)
self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data")
self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data")

View File

@@ -15,14 +15,14 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
{

View File

@@ -7,7 +7,7 @@ frappe.query_reports["Bank Clearance Summary"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
width: "80",
},
{

View File

@@ -38,14 +38,14 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
{

View File

@@ -211,7 +211,8 @@ def get_conditions(filters):
if filters.get("account"):
filters.account = get_accounts_with_children(filters.account)
conditions.append("account in %(account)s")
if filters.account:
conditions.append("account in %(account)s")
if filters.get("cost_center"):
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
@@ -316,7 +317,7 @@ def get_accounts_with_children(accounts):
else:
frappe.throw(_("Account: {0} does not exist").format(d))
return list(set(all_accounts))
return list(set(all_accounts)) if all_accounts else None
def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):

View File

@@ -15,14 +15,14 @@ frappe.query_reports["Gross Profit"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
{

View File

@@ -5,14 +5,15 @@
import frappe
from frappe import _
from frappe.utils import flt
from pypika import Order
import erpnext
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (
add_sub_total_row,
add_total_row,
apply_group_by_conditions,
get_grand_total,
get_group_by_and_display_fields,
get_group_by_conditions,
get_tax_accounts,
)
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
@@ -29,7 +30,7 @@ def _execute(filters=None, additional_table_columns=None):
company_currency = erpnext.get_company_currency(filters.company)
item_list = get_items(filters, get_query_columns(additional_table_columns))
item_list = get_items(filters, additional_table_columns)
aii_account_map = get_aii_accounts()
if item_list:
itemised_tax, tax_columns = get_tax_accounts(
@@ -287,59 +288,87 @@ def get_columns(additional_table_columns, filters):
return columns
def get_conditions(filters):
conditions = ""
def apply_conditions(query, pi, pii, filters):
for opts in ("company", "supplier", "item_code", "mode_of_payment"):
if filters.get(opts):
query = query.where(pi[opts] == filters[opts])
for opts in (
("company", " and `tabPurchase Invoice`.company=%(company)s"),
("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"),
("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"),
("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"),
("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s"),
("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"),
):
if filters.get(opts[0]):
conditions += opts[1]
if filters.get("from_date"):
query = query.where(pi.posting_date >= filters.get("from_date"))
if filters.get("to_date"):
query = query.where(pi.posting_date <= filters.get("to_date"))
if filters.get("item_group"):
query = query.where(pii.item_group == filters.get("item_group"))
if not filters.get("group_by"):
conditions += (
"ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc"
)
query = query.orderby(pi.posting_date, order=Order.desc)
query = query.orderby(pii.item_group, order=Order.desc)
else:
conditions += get_group_by_conditions(filters, "Purchase Invoice")
query = apply_group_by_conditions(filters, "Purchase Invoice")
return conditions
return query
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
if additional_query_columns:
additional_query_columns = "," + ",".join(additional_query_columns)
return frappe.db.sql(
"""
select
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice`.unrealized_profit_loss_account,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group`
,`tabPurchase Invoice Item`.`item_group` as pi_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
`tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`,
`tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {}
from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabItem`.name = `tabPurchase Invoice Item`.`item_code` and
`tabPurchase Invoice`.docstatus = 1 {}
""".format(additional_query_columns, conditions),
filters,
as_dict=1,
def get_items(filters, additional_table_columns):
pi = frappe.qb.DocType("Purchase Invoice")
pii = frappe.qb.DocType("Purchase Invoice Item")
Item = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(pi)
.join(pii)
.on(pi.name == pii.parent)
.left_join(Item)
.on(pii.item_code == Item.name)
.select(
pii.name,
pii.parent,
pi.posting_date,
pi.credit_to,
pi.company,
pi.supplier,
pi.remarks,
pi.base_net_total,
pi.unrealized_profit_loss_account,
pii.item_code,
pii.description,
pii.item_group,
pii.item_name.as_("pi_item_name"),
pii.item_group.as_("pi_item_group"),
Item.item_name.as_("i_item_name"),
Item.item_group.as_("i_item_group"),
pii.project,
pii.purchase_order,
pii.purchase_receipt,
pii.po_detail,
pii.expense_account,
pii.stock_qty,
pii.stock_uom,
pii.base_net_amount,
pi.supplier_name,
pi.mode_of_payment,
)
.where(pi.docstatus == 1)
)
if filters.get("supplier"):
query = query.where(pi.supplier == filters["supplier"])
if filters.get("company"):
query = query.where(pi.company == filters["company"])
if additional_table_columns:
for column in additional_table_columns:
if column.get("_doctype"):
table = frappe.qb.DocType(column.get("_doctype"))
query = query.select(table[column.get("fieldname")])
else:
query = query.select(pi[column.get("fieldname")])
query = apply_conditions(query, pi, pii, filters)
return query.run(as_dict=True)
def get_aii_accounts():
return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany"))

View File

@@ -41,6 +41,12 @@ frappe.query_reports["Item-wise Sales Register"] = {
label: __("Warehouse"),
fieldtype: "Link",
options: "Warehouse",
get_query: function () {
const company = frappe.query_report.get_filter_value("company");
return {
filters: { company: company },
};
},
},
{
fieldname: "brand",

View File

@@ -7,6 +7,7 @@ from frappe import _
from frappe.model.meta import get_field_precision
from frappe.utils import cstr, flt
from frappe.utils.xlsxutils import handle_html
from pypika import Order
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
@@ -26,7 +27,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions)
item_list = get_items(filters, additional_table_columns, additional_conditions)
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
@@ -83,9 +84,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
"company": d.company,
"sales_order": d.sales_order,
"delivery_note": d.delivery_note,
"income_account": d.unrealized_profit_loss_account
if d.is_internal_customer == 1
else d.income_account,
"income_account": get_income_account(d),
"cost_center": d.cost_center,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
@@ -150,6 +149,15 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
return columns, data, None, None, None, skip_total_row
def get_income_account(row):
if row.enable_deferred_revenue:
return row.deferred_revenue_account
elif row.is_internal_customer == 1:
return row.unrealized_profit_loss_account
else:
return row.income_account
def get_columns(additional_table_columns, filters):
columns = []
@@ -333,93 +341,140 @@ def get_columns(additional_table_columns, filters):
return columns
def get_conditions(filters, additional_conditions=None):
conditions = ""
def apply_conditions(query, si, sii, filters, additional_conditions=None):
for opts in ("company", "customer", "item_code"):
if filters.get(opts):
query = query.where(si[opts] == filters[opts])
for opts in (
("company", " and `tabSales Invoice`.company=%(company)s"),
("customer", " and `tabSales Invoice`.customer = %(customer)s"),
("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"),
("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"),
("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s"),
):
if filters.get(opts[0]):
conditions += opts[1]
if filters.get("from_date"):
query = query.where(si.posting_date >= filters.get("from_date"))
if additional_conditions:
conditions += additional_conditions
if filters.get("to_date"):
query = query.where(si.posting_date <= filters.get("to_date"))
if filters.get("mode_of_payment"):
conditions += """ and exists(select name from `tabSales Invoice Payment`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
sales_invoice = frappe.db.get_all(
"Sales Invoice Payment", {"mode_of_payment": filters.get("mode_of_payment")}, pluck="parent"
)
query = query.where(si.name.isin(sales_invoice))
if filters.get("warehouse"):
if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
lft, rgt = frappe.db.get_all(
"Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True
)[0]
conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) "
warehouses = frappe.db.get_all("Warehouse", {"lft": (">", lft), "rgt": ("<", rgt)}, pluck="name")
query = query.where(sii.warehouse.isin(warehouses))
else:
conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
query = query.where(sii.warehouse == filters.get("warehouse"))
if filters.get("brand"):
conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
query = query.where(sii.brand == filters.get("brand"))
if filters.get("item_group"):
conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
query = query.where(sii.item_group == filters.get("item_group"))
if filters.get("income_account"):
query = query.where(
(sii.income_account == filters.get("income_account"))
| (sii.deferred_revenue_account == filters.get("income_account"))
| (si.unrealized_profit_loss_account == filters.get("income_account"))
)
if not filters.get("group_by"):
conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
query = query.orderby(si.posting_date, order=Order.desc)
query = query.orderby(sii.item_group, order=Order.desc)
else:
conditions += get_group_by_conditions(filters, "Sales Invoice")
query = apply_group_by_conditions(query, si, sii, filters)
return conditions
for key, value in (additional_conditions or {}).items():
query = query.where(si[key] == value)
return query
def get_group_by_conditions(filters, doctype):
def apply_group_by_conditions(query, si, ii, filters):
if filters.get("group_by") == "Invoice":
return f"ORDER BY `tab{doctype} Item`.parent desc"
query = query.orderby(ii.parent, order=Order.desc)
elif filters.get("group_by") == "Item":
return f"ORDER BY `tab{doctype} Item`.`item_code`"
query = query.orderby(ii.item_code)
elif filters.get("group_by") == "Item Group":
return "ORDER BY `tab{} Item`.{}".format(doctype, frappe.scrub(filters.get("group_by")))
query = query.orderby(ii.item_group)
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by")))
query = query.orderby(si[frappe.scrub(filters.get("group_by"))])
return query
def get_items(filters, additional_query_columns, additional_conditions=None):
conditions = get_conditions(filters, additional_conditions)
si = frappe.qb.DocType("Sales Invoice")
sii = frappe.qb.DocType("Sales Invoice Item")
item = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(si)
.join(sii)
.on(si.name == sii.parent)
.left_join(item)
.on(sii.item_code == item.name)
.select(
sii.name,
sii.parent,
si.posting_date,
si.debit_to,
si.unrealized_profit_loss_account,
si.is_internal_customer,
si.customer,
si.remarks,
si.territory,
si.company,
si.base_net_total,
sii.project,
sii.item_code,
sii.description,
sii.item_name,
sii.item_group,
sii.item_name.as_("si_item_name"),
sii.item_group.as_("si_item_group"),
item.item_name.as_("i_item_name"),
item.item_group.as_("i_item_group"),
sii.sales_order,
sii.delivery_note,
sii.income_account,
sii.cost_center,
sii.enable_deferred_revenue,
sii.deferred_revenue_account,
sii.stock_qty,
sii.stock_uom,
sii.base_net_rate,
sii.base_net_amount,
si.customer_name,
si.customer_group,
sii.so_detail,
si.update_stock,
sii.uom,
sii.qty,
)
.where(si.docstatus == 1)
)
if additional_query_columns:
additional_query_columns = "," + ",".join(additional_query_columns)
return frappe.db.sql(
"""
select
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.unrealized_profit_loss_account,
`tabSales Invoice`.is_internal_customer,
`tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.project,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
`tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {}
from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and
`tabItem`.name = `tabSales Invoice Item`.`item_code` and
`tabSales Invoice`.docstatus = 1 {}
""".format(additional_query_columns, conditions),
filters,
as_dict=1,
) # nosec
for column in additional_query_columns:
if column.get("_doctype"):
table = frappe.qb.DocType(column.get("_doctype"))
query = query.select(table[column.get("fieldname")])
else:
query = query.select(si[column.get("fieldname")])
if filters.get("customer"):
query = query.where(si.customer == filters["customer"])
if filters.get("customer_group"):
query = query.where(si.customer_group == filters["customer_group"])
query = apply_conditions(query, si, sii, filters, additional_conditions)
return query.run(as_dict=True)
def get_delivery_notes_against_sales_order(item_list):
@@ -427,16 +482,14 @@ def get_delivery_notes_against_sales_order(item_list):
so_item_rows = list(set([d.so_detail for d in item_list]))
if so_item_rows:
delivery_notes = frappe.db.sql(
"""
select parent, so_detail
from `tabDelivery Note Item`
where docstatus=1 and so_detail in (%s)
group by so_detail, parent
"""
% (", ".join(["%s"] * len(so_item_rows))),
tuple(so_item_rows),
as_dict=1,
dn_item = frappe.qb.DocType("Delivery Note Item")
delivery_notes = (
frappe.qb.from_(dn_item)
.select(dn_item.parent, dn_item.so_detail)
.where(dn_item.docstatus == 1)
.where(dn_item.so_detail.isin(so_item_rows))
.groupby(dn_item.so_detail, dn_item.parent)
.run(as_dict=True)
)
for dn in delivery_notes:
@@ -446,15 +499,16 @@ def get_delivery_notes_against_sales_order(item_list):
def get_grand_total(filters, doctype):
return frappe.db.sql(
f""" SELECT
SUM(`tab{doctype}`.base_grand_total)
FROM `tab{doctype}`
WHERE `tab{doctype}`.docstatus = 1
and posting_date between %s and %s
""",
(filters.get("from_date"), filters.get("to_date")),
)[0][0] # nosec
return flt(
frappe.db.get_value(
doctype,
{
"docstatus": 1,
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
},
"sum(base_grand_total)",
)
)
def get_tax_accounts(

View File

@@ -15,7 +15,7 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",

View File

@@ -3,7 +3,9 @@
import frappe
from frappe import _
from frappe import _, qb
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Abs
from frappe.utils import flt, getdate
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
@@ -21,16 +23,12 @@ def execute(filters=None):
data = []
for d in entries:
invoice = invoice_details.get(d.against_voucher) or frappe._dict()
if d.reference_type == "Purchase Invoice":
payment_amount = flt(d.debit) or -1 * flt(d.credit)
else:
payment_amount = flt(d.credit) or -1 * flt(d.debit)
invoice = invoice_details.get(d.against_voucher_no) or frappe._dict()
payment_amount = d.amount
d.update({"range1": 0, "range2": 0, "range3": 0, "range4": 0, "outstanding": payment_amount})
if d.against_voucher:
if d.against_voucher_no:
ReceivablePayableReport(filters).get_ageing_data(invoice.posting_date, d)
row = [
@@ -39,11 +37,10 @@ def execute(filters=None):
d.party_type,
d.party,
d.posting_date,
d.against_voucher,
d.against_voucher_no,
invoice.posting_date,
invoice.due_date,
d.debit,
d.credit,
d.amount,
d.remarks,
d.age,
d.range1,
@@ -111,8 +108,7 @@ def get_columns(filters):
"width": 100,
},
{"fieldname": "due_date", "label": _("Payment Due Date"), "fieldtype": "Date", "width": 100},
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 140},
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140},
{"fieldname": "amount", "label": _("Amount"), "fieldtype": "Currency", "width": 140},
{"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200},
{"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50},
{"fieldname": "range1", "label": _("0-30"), "fieldtype": "Currency", "width": 140},
@@ -129,51 +125,68 @@ def get_columns(filters):
def get_conditions(filters):
ple = qb.DocType("Payment Ledger Entry")
conditions = []
if not filters.party_type:
if filters.payment_type == _("Outgoing"):
filters.party_type = "Supplier"
else:
filters.party_type = "Customer"
if filters.party_type:
conditions.append("party_type=%(party_type)s")
conditions.append(ple.delinked.eq(0))
if filters.payment_type == _("Outgoing"):
conditions.append(ple.party_type.eq("Supplier"))
conditions.append(ple.against_voucher_type.eq("Purchase Invoice"))
else:
conditions.append(ple.party_type.eq("Customer"))
conditions.append(ple.against_voucher_type.eq("Sales Invoice"))
if filters.party:
conditions.append("party=%(party)s")
if filters.party_type:
conditions.append("against_voucher_type=%(reference_type)s")
filters["reference_type"] = (
"Sales Invoice" if filters.party_type == "Customer" else "Purchase Invoice"
)
conditions.append(ple.party.eq(filters.party))
if filters.get("from_date"):
conditions.append("posting_date >= %(from_date)s")
conditions.append(ple.posting_date.gte(filters.get("from_date")))
if filters.get("to_date"):
conditions.append("posting_date <= %(to_date)s")
conditions.append(ple.posting_date.lte(filters.get("to_date")))
return "and " + " and ".join(conditions) if conditions else ""
if filters.get("company"):
conditions.append(ple.company.eq(filters.get("company")))
return conditions
def get_entries(filters):
return frappe.db.sql(
"""select
voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher
from `tabGL Entry`
where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') and is_cancelled = 0 {}
""".format(get_conditions(filters)),
filters,
as_dict=1,
ple = qb.DocType("Payment Ledger Entry")
conditions = get_conditions(filters)
query = (
qb.from_(ple)
.select(
ple.voucher_type,
ple.voucher_no,
ple.party_type,
ple.party,
ple.posting_date,
Abs(ple.amount).as_("amount"),
ple.remarks,
ple.against_voucher_no,
)
.where(Criterion.all(conditions))
)
res = query.run(as_dict=True)
return res
def get_invoice_posting_date_map(filters):
invoice_details = {}
dt = "Sales Invoice" if filters.get("payment_type") == _("Incoming") else "Purchase Invoice"
for t in frappe.db.sql(f"select name, posting_date, due_date from `tab{dt}`", as_dict=1):
dt = (
qb.DocType("Sales Invoice")
if filters.get("payment_type") == _("Incoming")
else qb.DocType("Purchase Invoice")
)
res = (
qb.from_(dt)
.select(dt.name, dt.posting_date, dt.due_date)
.where((dt.docstatus.eq(1)) & (dt.company.eq(filters.get("company"))))
.run(as_dict=1)
)
for t in res:
invoice_details[t.name] = t
return invoice_details

View File

@@ -59,13 +59,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
},
{
fieldname: "show_zero_values",

View File

@@ -37,13 +37,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
},
{
fieldname: "cost_center",

View File

@@ -36,13 +36,13 @@ frappe.query_reports["Trial Balance for Party"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
},
{
fieldname: "party_type",

View File

@@ -55,6 +55,9 @@ GL_REPOSTING_CHUNK = 100
def get_fiscal_year(
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
):
if isinstance(boolean, str):
boolean = loads(boolean)
fiscal_years = get_fiscal_years(
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
)

View File

@@ -78,7 +78,7 @@ frappe.ui.form.on("Asset", {
frm.events.make_schedules_editable(frm);
if (frm.doc.docstatus == 1) {
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
if (["Submitted", "Partially Depreciated", "Fully Depreciated"].includes(frm.doc.status)) {
frm.add_custom_button(
__("Transfer Asset"),
function () {
@@ -280,7 +280,7 @@ frappe.ui.form.on("Asset", {
if (v.journal_entry) {
asset_values.push(asset_value);
} else {
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
if (["Scrapped", "Sold"].includes(frm.doc.status)) {
asset_values.push(null);
} else {
asset_values.push(asset_value);
@@ -312,7 +312,7 @@ frappe.ui.form.on("Asset", {
});
}
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
if (["Scrapped", "Sold"].includes(frm.doc.status)) {
x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: "Date" }));
asset_values.push(0);
}

View File

@@ -12,6 +12,7 @@ from frappe.utils import (
add_months,
add_years,
cint,
cstr,
date_diff,
flt,
get_datetime,
@@ -361,9 +362,11 @@ class Asset(AccountsController):
final_number_of_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
self.number_of_depreciations_booked
)
has_pro_rata = self.check_is_pro_rata(finance_book)
if has_pro_rata:
depr_already_booked = any(
[d.journal_entry for d in self.get("schedules") if d.finance_book == finance_book.finance_book]
)
if has_pro_rata and not depr_already_booked:
final_number_of_depreciations += 1
has_wdv_or_dd_non_yearly_pro_rata = False
@@ -543,7 +546,7 @@ class Asset(AccountsController):
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx,
"finance_book_id": cstr(finance_book.idx),
"shift": shift,
},
)
@@ -749,7 +752,6 @@ class Asset(AccountsController):
):
straight_line_idx = []
finance_books = []
for i, d in enumerate(self.get("schedules")):
if ignore_booked_entry and d.journal_entry:
continue
@@ -771,7 +773,10 @@ class Asset(AccountsController):
finance_books.append(int(d.finance_book_id))
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
value_after_depreciation -= flt(depreciation_amount)
if not d.journal_entry:
value_after_depreciation = flt(
flt(value_after_depreciation) - depreciation_amount, d.precision("depreciation_amount")
)
# for the last row, if depreciation method = Straight Line
if (
@@ -783,10 +788,13 @@ class Asset(AccountsController):
book = self.get("finance_books")[cint(d.finance_book_id) - 1]
if not book.shift_based:
depreciation_amount += flt(
adjustment_amount = flt(
value_after_depreciation - flt(book.expected_value_after_useful_life),
d.precision("depreciation_amount"),
)
depreciation_amount = flt(
depreciation_amount + adjustment_amount, d.precision("depreciation_amount")
)
d.depreciation_amount = depreciation_amount
accumulated_depreciation += d.depreciation_amount
@@ -1433,7 +1441,7 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx, number_of_
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
elif asset.flags.increase_in_asset_value_due_to_repair:
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
number_of_pending_depreciations
)
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:

View File

@@ -1355,9 +1355,9 @@ class TestDepreciationBasics(AssetSetup):
for schedule in asset.schedules:
if schedule.idx <= 3:
self.assertEqual(schedule.finance_book_id, 1)
self.assertEqual(schedule.finance_book_id, "1")
else:
self.assertEqual(schedule.finance_book_id, 2)
self.assertEqual(schedule.finance_book_id, "2")
def test_depreciation_entry_cancellation(self):
asset = create_asset(

View File

@@ -103,12 +103,11 @@ class TestAssetValueAdjustment(unittest.TestCase):
["2023-05-31", 9983.33, 45408.05],
["2023-06-30", 9983.33, 55391.38],
["2023-07-31", 9983.33, 65374.71],
["2023-08-31", 8300.0, 73674.71],
["2023-09-30", 8300.0, 81974.71],
["2023-10-31", 8300.0, 90274.71],
["2023-11-30", 8300.0, 98574.71],
["2023-12-31", 8300.0, 106874.71],
["2024-01-15", 8300.0, 115174.71],
["2023-08-31", 9960.0, 75334.71],
["2023-09-30", 9960.0, 85294.71],
["2023-10-31", 9960.0, 95254.71],
["2023-11-30", 9960.0, 105214.71],
["2023-12-15", 9960.0, 115174.71],
]
schedules = [

View File

@@ -6,8 +6,8 @@ frappe.provide("erpnext.accounts.dimensions");
{% include 'erpnext/public/js/controllers/buying.js' %};
frappe.ui.form.on("Purchase Order", {
setup: function(frm) {
setup: function (frm) {
frm.ignore_doctypes_on_cancel_all = ["Unreconcile Payment", "Unreconcile Payment Entries"];
if (frm.doc.is_old_subcontracting_flow) {
frm.set_query("reserve_warehouse", "supplied_items", function() {
return {
@@ -180,7 +180,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
this.frm.fields_dict.items_section.wrapper.removeClass("hide-border");
}
if(!in_list(["Closed", "Delivered"], doc.status)) {
if(!["Closed", "Delivered"].includes(doc.status)) {
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
// Don't add Update Items button if the PO is following the new subcontracting flow.
if (!(this.frm.doc.is_subcontracted && !this.frm.doc.is_old_subcontracting_flow)) {
@@ -211,7 +211,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
this.frm.page.set_inner_btn_group_as_primary(__("Status"));
}
} else if(in_list(["Closed", "Delivered"], doc.status)) {
} else if(["Closed", "Delivered"].includes(doc.status)) {
if (this.frm.has_perm("submit")) {
this.frm.add_custom_button(__('Re-open'), () => this.unclose_purchase_order(), __("Status"));
}

View File

@@ -345,7 +345,13 @@ class PurchaseOrder(BuyingController):
update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry")
self.ignore_linked_doctypes = (
"GL Entry",
"Payment Ledger Entry",
"Unreconcile Payment",
"Unreconcile Payment Entries",
)
super().on_cancel()
if self.is_against_so():

View File

@@ -27,13 +27,13 @@ frappe.query_reports["Procurement Tracker"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
},
],
};

View File

@@ -35,14 +35,14 @@ frappe.query_reports["Purchase Analytics"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
{

View File

@@ -8,6 +8,7 @@ from frappe.contacts.doctype.address.address import render_address
from frappe.utils import cint, cstr, flt, getdate
from frappe.utils.data import nowtime
import erpnext
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.party import get_party_details
from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
@@ -305,6 +306,8 @@ class BuyingController(SubcontractingController):
else:
item.valuation_rate = 0.0
update_regional_item_valuation_rate(self)
def set_incoming_rate(self):
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
return
@@ -894,3 +897,8 @@ def validate_item_type(doc, fieldname, message):
).format(items, message)
frappe.throw(error_message)
@erpnext.allow_regional
def update_regional_item_valuation_rate(doc):
pass

View File

@@ -51,7 +51,7 @@ class StockController(AccountsController):
self.validate_internal_transfer()
self.validate_putaway_capacity()
def make_gl_entries(self, gl_entries=None, from_repost=False):
def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False):
if self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@@ -72,7 +72,11 @@ class StockController(AccountsController):
if self.docstatus == 1:
if not gl_entries:
gl_entries = self.get_gl_entries(warehouse_account)
gl_entries = (
self.get_gl_entries(warehouse_account, via_landed_cost_voucher)
if self.doctype == "Purchase Receipt"
else self.get_gl_entries(warehouse_account)
)
make_gl_entries(gl_entries, from_repost=from_repost)
def validate_serialized_batch(self):

View File

@@ -6,13 +6,13 @@ frappe.query_reports["Campaign Efficiency"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
},
],
};

View File

@@ -6,13 +6,13 @@ frappe.query_reports["Lead Owner Efficiency"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
},
],
};

View File

@@ -400,7 +400,7 @@ frappe.ui.form.on("BOM", {
},
rm_cost_as_per(frm) {
if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) {
if (["Valuation Rate", "Last Purchase Rate"].includes(frm.doc.rm_cost_as_per)) {
frm.set_value("plc_conversion_rate", 1.0);
}
},

View File

@@ -129,7 +129,7 @@ frappe.ui.form.on("Production Plan", {
if (
frm.doc.mr_items &&
frm.doc.mr_items.length &&
!in_list(["Material Requested", "Closed"], frm.doc.status)
!["Material Requested", "Closed"].includes(frm.doc.status)
) {
frm.add_custom_button(
__("Material Request"),

View File

@@ -194,7 +194,7 @@ frappe.ui.form.on("Work Order", {
},
add_custom_button_to_return_components: function (frm) {
if (frm.doc.docstatus === 1 && in_list(["Closed", "Completed"], frm.doc.status)) {
if (frm.doc.docstatus === 1 && ["Closed", "Completed"].includes(frm.doc.status)) {
let non_consumed_items = frm.doc.required_items.filter((d) => {
return flt(d.consumed_qty) < flt(d.transferred_qty - d.returned_qty);
});
@@ -594,7 +594,7 @@ erpnext.work_order = {
);
}
if (doc.docstatus === 1 && !in_list(["Closed", "Completed"], doc.status)) {
if (doc.docstatus === 1 && !["Closed", "Completed"].includes(doc.status)) {
if (doc.status != "Stopped" && doc.status != "Completed") {
frm.add_custom_button(
__("Stop"),

View File

@@ -21,7 +21,8 @@ def get_exploded_items(bom, data, indent=0, qty=1):
exploded_items = frappe.get_all(
"BOM Item",
filters={"parent": bom},
fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom"],
fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom", "idx"],
order_by="idx ASC",
)
for item in exploded_items:

View File

@@ -37,14 +37,14 @@ frappe.query_reports["Job Card Summary"] = {
label: __("From Posting Date"),
fieldname: "from_date",
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
},
{
label: __("To Posting Date"),
fieldname: "to_date",
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
{

View File

@@ -16,14 +16,14 @@ frappe.query_reports["Production Analytics"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
{

View File

@@ -362,4 +362,5 @@ erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2
erpnext.patches.v14_0.set_maintain_stock_for_bom_item
execute:frappe.db.set_single_value('E Commerce Settings', 'show_actual_qty', 1)
erpnext.patches.v14_0.delete_orphaned_asset_movement_item_records
erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1

View File

@@ -0,0 +1,10 @@
import frappe
def execute():
pr_table = frappe.qb.DocType("Pricing Rule")
(
frappe.qb.update(pr_table)
.set(pr_table.has_priority, 1)
.where((pr_table.priority.isnotnull()) & (pr_table.priority != ""))
).run()

View File

@@ -9,15 +9,13 @@ def execute():
dt = frappe.qb.DocType(doctype)
records = (
frappe.qb.from_(dt)
.select(dt.name, dt.notes, dt.modified_by, dt.modified)
.where(dt.notes.isnotnull() & dt.notes != "")
frappe.qb.from_(dt).select(dt.name, dt.notes).where(dt.notes.isnotnull() & dt.notes != "")
).run(as_dict=True)
for d in records:
if strip_html(cstr(d.notes)).strip():
doc = frappe.get_doc(doctype, d.name)
doc.append("notes", {"note": d.notes, "added_by": d.modified_by, "added_on": d.modified})
doc.append("notes", {"note": d.notes})
doc.update_child_table("notes")
frappe.db.sql_ddl(f"alter table `tab{doctype}` drop column `notes`")

View File

@@ -57,6 +57,14 @@ frappe.ui.form.on("Project", {
filters: filters,
};
});
frm.set_query("cost_center", () => {
return {
filters: {
company: frm.doc.company,
},
};
});
},
refresh: function (frm) {

View File

@@ -240,7 +240,7 @@ erpnext.AccountTreeGrid = class AccountTreeGrid extends frappe.views.TreeGridRep
flt(account.closing_dr) -
flt(account.closing_cr);
me.set_debit_or_credit(parent_account, "closing", bal);
} else if (in_list(["debit", "credit"], col.field)) {
} else if (["debit", "credit"].includes(col.field)) {
parent_account[col.field] =
flt(parent_account[col.field]) + flt(account[col.field]);
}

View File

@@ -20,7 +20,7 @@ frappe.ui.form.on("Communication", {
);
}
if (!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
if (!["Lead", "Opportunity"].includes(frm.doc.reference_doctype)) {
frm.add_custom_button(
__("Lead"),
() => {

View File

@@ -9,7 +9,7 @@ frappe.ui.form.on(cur_frm.doctype, {
setup: function(frm) {
// set conditional display for rate column in taxes
$(frm.wrapper).on('grid-row-render', function(e, grid_row) {
if(in_list(['Sales Taxes and Charges', 'Purchase Taxes and Charges'], grid_row.doc.doctype)) {
if(['Sales Taxes and Charges', 'Purchase Taxes and Charges'].includes(grid_row.doc.doctype)) {
erpnext.taxes.set_conditional_mandatory_rate_or_amount(grid_row);
}
});

View File

@@ -136,7 +136,7 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac
}
toggle_subcontracting_fields() {
if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) {
if (['Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty',
'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM');

View File

@@ -9,7 +9,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
apply_pricing_rule_on_item(item) {
let effective_item_rate = item.price_list_rate;
let item_rate = item.rate;
if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
if (["Sales Order", "Quotation"].includes(item.parenttype) && item.blanket_order_rate) {
effective_item_rate = item.blanket_order_rate;
}
if (item.margin_type == "Percentage") {
@@ -52,7 +52,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
// Advance calculation applicable to Sales/Purchase Invoice
if (
in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)
&& this.frm.doc.docstatus < 2
&& !this.frm.doc.is_return
) {
@@ -60,7 +60,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
if (
in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)
&& this.frm.doc.is_pos
&& this.frm.doc.is_return
) {
@@ -69,7 +69,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
// Sales person's commission
if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
if (["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"].includes(this.frm.doc.doctype)) {
this.calculate_commission();
this.calculate_contribution();
}
@@ -547,7 +547,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment)
: this.frm.doc.net_total);
if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
this.frm.doc.base_grand_total = (this.frm.doc.total_taxes_and_charges) ?
flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total;
} else {
@@ -555,7 +555,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.doc.taxes_and_charges_added = this.frm.doc.taxes_and_charges_deducted = 0.0;
if(tax_count) {
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
if (in_list(["Valuation and Total", "Total"], tax.category)) {
if (["Valuation and Total", "Total"].includes(tax.category)) {
if(tax.add_deduct_tax == "Add") {
me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount);
} else {
@@ -702,7 +702,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
var actual_taxes_dict = {};
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) {
if (["Actual", "On Item Quantity"].includes(tax.charge_type)) {
var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount;
tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
actual_taxes_dict[tax.idx] = tax_amount;
@@ -747,7 +747,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
// NOTE:
// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
// total_advance is only for non POS Invoice
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_return){
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_return){
this.calculate_paid_amount();
}
@@ -755,7 +755,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) {
if(["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
@@ -778,7 +778,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.refresh_field("base_paid_amount");
}
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount)
? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total"))
: total_amount_to_pay;
@@ -882,7 +882,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
calculate_change_amount(){
this.frm.doc.change_amount = 0.0;
this.frm.doc.base_change_amount = 0.0;
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)
&& this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) {
var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; });

View File

@@ -260,7 +260,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
setup_quality_inspection() {
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
if(!["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
return;
}
@@ -272,7 +272,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
}
const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)
const inspection_type = ["Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype)
? "Incoming" : "Outgoing";
let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
@@ -304,7 +304,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
make_payment_request() {
let me = this;
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
const payment_request_type = (['Sales Order', 'Sales Invoice'].includes(this.frm.doc.doctype))
? "Inward" : "Outward";
frappe.call({
@@ -417,7 +417,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
setup_sms() {
var me = this;
let blacklist = ['Purchase Invoice', 'BOM'];
if(this.frm.doc.docstatus===1 && !in_list(["Lost", "Stopped", "Closed"], this.frm.doc.status)
if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
&& !blacklist.includes(this.frm.doctype)) {
this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); });
}
@@ -780,7 +780,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
var set_party_account = function(set_pricing) {
if (in_list(["Sales Invoice", "Purchase Invoice"], me.frm.doc.doctype)) {
if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
if(me.frm.doc.doctype=="Sales Invoice") {
var party_type = "Customer";
var party_account_field = 'debit_to';
@@ -815,7 +815,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doctype)) {
erpnext.utils.get_shipping_address(this.frm, function() {
set_party_account(set_pricing);
});
@@ -1482,7 +1482,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"doctype": me.frm.doc.doctype,
"name": me.frm.doc.name,
"is_return": cint(me.frm.doc.is_return),
"update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
"conversion_factor": me.frm.doc.conversion_factor,
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
"coupon_code": me.frm.doc.coupon_code
@@ -2126,7 +2126,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
get_method_for_payment() {
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
if(in_list(['Sales Invoice', 'Purchase Invoice'], cur_frm.doc.doctype)){
if(['Sales Invoice', 'Purchase Invoice'].includes( cur_frm.doc.doctype)){
method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice";
}else {
method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order";

View File

@@ -218,7 +218,7 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
update_paid_amount(update_write_off) {
var me = this;
if (in_list(["change_amount", "write_off_amount"], this.idx)) {
if (["change_amount", "write_off_amount"].includes(this.idx)) {
var value = me.selected_mode.val();
if (me.idx == "change_amount") {
me.change_amount(value);

View File

@@ -28,11 +28,11 @@ erpnext.SMSManager = function SMSManager(doc) {
"Purchase Receipt": "Items has been received against purchase receipt: " + doc.name,
};
if (in_list(["Sales Order", "Delivery Note", "Sales Invoice"], doc.doctype))
if (["Sales Order", "Delivery Note", "Sales Invoice"].includes(doc.doctype))
this.show(doc.contact_person, "Customer", doc.customer, "", default_msg[doc.doctype]);
else if (doc.doctype === "Quotation")
this.show(doc.contact_person, "Customer", doc.party_name, "", default_msg[doc.doctype]);
else if (in_list(["Purchase Order", "Purchase Receipt"], doc.doctype))
else if (["Purchase Order", "Purchase Receipt"].includes(doc.doctype))
this.show(doc.contact_person, "Supplier", doc.supplier, "", default_msg[doc.doctype]);
else if (doc.doctype == "Lead") this.show("", "", "", doc.mobile_no, default_msg[doc.doctype]);
else if (doc.doctype == "Opportunity")

View File

@@ -12,6 +12,7 @@
{% for(var i=0, l=notes.length; i<l; i++) { %}
<div class="comment-content p-3 row" name="{{ notes[i].name }}">
<div class="mb-2 head col-xs-3">
{% if (notes[i].added_by && notes[i].added_on) %}
<div class="row">
<div class="col-xs-2">
{{ frappe.avatar(notes[i].added_by) }}
@@ -25,6 +26,7 @@
</div>
</div>
</div>
{% } %}
</div>
<div class="content col-xs-8">
{{ notes[i].note }}

View File

@@ -410,11 +410,13 @@ $.extend(erpnext.utils, {
method: "erpnext.accounts.utils.get_fiscal_year",
args: {
date: date,
boolean: boolean,
},
async: false,
callback: function (r) {
if (r.message) {
fiscal_year = r.message[0];
if (with_dates) fiscal_year = r.message;
else fiscal_year = r.message[0];
}
},
});

View File

@@ -14,10 +14,10 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) {
if (!args) {
if (
(frm.doctype != "Purchase Order" && frm.doc.customer) ||
(frm.doc.party_name && in_list(["Quotation", "Opportunity"], frm.doc.doctype))
(frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype))
) {
let party_type = "Customer";
if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) {
if (frm.doc.quotation_to && ["Lead", "Prospect"].includes(frm.doc.quotation_to)) {
party_type = frm.doc.quotation_to;
}

View File

@@ -446,7 +446,7 @@ body.product-page {
width: 30%;
@media (max-width: 992px) {
width: 40%;
width: 50%;
}
}
}

View File

@@ -34,7 +34,7 @@ frappe.ui.form.on("Import Supplier Invoice", {
},
toggle_read_only_fields: function (frm) {
if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) {
if (["File Import Completed", "Processing File Data"].includes(frm.doc.status)) {
cur_frm.set_read_only();
cur_frm.refresh_fields();
frm.set_df_property("import_invoices", "hidden", 1);

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.test_runner import make_test_records
@@ -322,7 +324,7 @@ class TestCustomer(FrappeTestCase):
frappe.ValidationError,
update_child_qty_rate,
so.doctype,
frappe.json.dumps([modified_item]),
json.dumps([modified_item]),
so.name,
)

View File

@@ -241,7 +241,11 @@ frappe.ui.form.on("Sales Order", {
frm.set_value("advance_paid", 0)
}
frm.ignore_doctypes_on_cancel_all = ['Purchase Order'];
frm.ignore_doctypes_on_cancel_all = [
"Purchase Order",
"Unreconcile Payment",
"Unreconcile Payment Entries",
];
},
delivery_date: function(frm) {

View File

@@ -1138,7 +1138,8 @@
"hide_seconds": 1,
"label": "Inter Company Order Reference",
"options": "Purchase Order",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "project",
@@ -1631,7 +1632,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2024-03-20 16:04:43.627183",
"modified": "2024-05-23 16:35:54.905804",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
@@ -1710,4 +1711,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -269,7 +269,13 @@ class SalesOrder(SellingController):
update_coupon_code_count(self.coupon_code, "used")
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Payment Ledger Entry",
"Unreconcile Payment",
"Unreconcile Payment Entries",
)
super().on_cancel()
# Cannot cancel closed SO
@@ -576,6 +582,11 @@ def get_requested_item_qty(sales_order):
def make_material_request(source_name, target_doc=None):
requested_item_qty = get_requested_item_qty(source_name)
def postprocess(source, target):
if source.tc_name and frappe.db.get_value("Terms and Conditions", source.tc_name, "buying") != 1:
target.tc_name = None
target.terms = None
def get_remaining_qty(so_item):
return flt(
flt(so_item.qty)
@@ -631,6 +642,7 @@ def make_material_request(source_name, target_doc=None):
},
},
target_doc,
postprocess,
)
return doc
@@ -932,11 +944,19 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
target.discount_amount = 0.0
target.inter_company_order_reference = ""
target.shipping_rule = ""
target.tc_name = ""
target.terms = ""
target.payment_terms_template = ""
target.payment_schedule = []
default_price_list = frappe.get_value("Supplier", supplier, "default_price_list")
if default_price_list:
target.buying_price_list = default_price_list
default_payment_terms = frappe.get_value("Supplier", supplier, "payment_terms")
if default_payment_terms:
target.payment_terms_template = default_payment_terms
if any(item.delivered_by_supplier == 1 for item in source.items):
if source.shipping_address_name:
target.shipping_address = source.shipping_address_name
@@ -988,7 +1008,6 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
"contact_person",
"taxes_and_charges",
"shipping_address",
"terms",
],
"validation": {"docstatus": ["=", 1]},
},
@@ -1056,6 +1075,10 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
target.discount_amount = 0.0
target.inter_company_order_reference = ""
target.shipping_rule = ""
target.tc_name = ""
target.terms = ""
target.payment_terms_template = ""
target.payment_schedule = []
if is_drop_ship_order(target):
target.customer = source.customer
@@ -1091,7 +1114,6 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
"contact_person",
"taxes_and_charges",
"shipping_address",
"terms",
],
"validation": {"docstatus": ["=", 1]},
},

View File

@@ -73,7 +73,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
const { status } = doc;
let indicator_color = "";
in_list(["Paid", "Consolidated"], status) && (indicator_color = "green");
["Paid", "Consolidated"].includes(status) && (indicator_color = "green");
status === "Draft" && (indicator_color = "red");
status === "Return" && (indicator_color = "grey");

View File

@@ -23,14 +23,14 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
],

View File

@@ -43,14 +43,14 @@ frappe.query_reports["Sales Analytics"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
{

View File

@@ -21,7 +21,7 @@ frappe.query_reports["Sales Person Commission Summary"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",

View File

@@ -20,7 +20,7 @@ frappe.query_reports["Sales Person-wise Transaction Summary"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
fieldname: "to_date",

View File

@@ -219,7 +219,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
serial_no: item.serial_no || "",
},
callback:function(r){
if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
if (['Delivery Note', 'Sales Invoice'].includes(doc.doctype)) {
if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
if (has_batch_no) {
me.set_batch_number(cdt, cdn);
@@ -332,7 +332,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
if ((doc.packed_items || []).length) {
$(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true);
if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
if (['Delivery Note', 'Sales Invoice'].includes(doc.doctype)) {
var help_msg = "<div class='alert alert-warning'>" +
__("For 'Product Bundle' items, Warehouse, Serial No and Batch No will be considered from the 'Packing List' table. If Warehouse and Batch No are same for all packing items for any 'Product Bundle' item, those values can be entered in the main Item table, values will be copied to 'Packing List' table.")+
"</div>";
@@ -340,7 +340,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
}
} else {
$(cur_frm.fields_dict.packing_list.row.wrapper).toggle(false);
if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
if (['Delivery Note', 'Sales Invoice'].includes(doc.doctype)) {
frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = '';
}
}
@@ -367,7 +367,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) {
super.conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate);
if(frappe.meta.get_docfield(cdt, "stock_qty", cdn) &&
in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
['Delivery Note', 'Sales Invoice'].includes(doc.doctype)) {
if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
this.set_batch_number(cdt, cdn);
}
@@ -376,7 +376,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
qty(doc, cdt, cdn) {
super.qty(doc, cdt, cdn);
if(in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
if(['Delivery Note', 'Sales Invoice'].includes(doc.doctype)) {
if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
this.set_batch_number(cdt, cdn);
}
@@ -440,7 +440,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
};
frappe.ui.form.on(cur_frm.doctype,"project", function(frm) {
if(in_list(["Delivery Note", "Sales Invoice"], frm.doc.doctype)) {
if(["Delivery Note", "Sales Invoice"].includes(frm.doc.doctype)) {
if(frm.doc.project) {
frappe.call({
method:'erpnext.projects.doctype.project.project.get_cost_center_name' ,

View File

@@ -12,10 +12,11 @@ frappe.ui.form.on("Company", {
}
});
}
frm.call("check_if_transactions_exist").then((r) => {
frm.toggle_enable("default_currency", !r.message);
});
if (!frm.doc.__islocal) {
frm.call("check_if_transactions_exist").then((r) => {
frm.toggle_enable("default_currency", !r.message);
});
}
},
setup: function (frm) {
frm.__rename_queue = "long";

View File

@@ -712,7 +712,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2023-10-23 10:19:24.322898",
"modified": "2024-05-27 17:32:49.057386",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
@@ -768,6 +768,10 @@
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"role": "Auditor",
"select": 1
}
],
"show_name_in_global_search": 1,

View File

@@ -90,7 +90,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2023-08-28 17:26:46.826501",
"modified": "2024-06-12 16:10:31.451257",
"modified_by": "Administrator",
"module": "Setup",
"name": "Department",
@@ -132,6 +132,10 @@
"role": "HR Manager",
"share": 1,
"write": 1
},
{
"role": "Employee",
"select": 1
}
],
"show_name_in_global_search": 1,

View File

@@ -8,7 +8,7 @@ frappe.ui.form.on("Closing Stock Balance", {
},
generate_closing_balance(frm) {
if (in_list(["Queued", "Failed"], frm.doc.status)) {
if (["Queued", "Failed"].includes(frm.doc.status)) {
frm.add_custom_button(__("Generate Closing Stock Balance"), () => {
frm.call({
method: "enqueue_job",

View File

@@ -1,9 +1,9 @@
frappe.listview_settings["Delivery Trip"] = {
add_fields: ["status"],
get_indicator: function (doc) {
if (in_list(["Cancelled", "Draft"], doc.status)) {
if (["Cancelled", "Draft"].includes(doc.status)) {
return [__(doc.status), "red", "status,=," + doc.status];
} else if (in_list(["In Transit", "Scheduled"], doc.status)) {
} else if (["In Transit", "Scheduled"].includes(doc.status)) {
return [__(doc.status), "orange", "status,=," + doc.status];
} else if (doc.status === "Completed") {
return [__(doc.status), "green", "status,=," + doc.status];

View File

@@ -5,7 +5,7 @@ import copy
import json
import frappe
from frappe import _
from frappe import _, bold
from frappe.model.document import Document
from frappe.utils import (
cint,
@@ -397,6 +397,13 @@ class Item(Document):
def validate_warehouse_for_reorder(self):
"""Validate Reorder level table for duplicate and conditional mandatory"""
warehouse_material_request_type: list[tuple[str, str]] = []
_warehouse_before_save = frappe._dict()
if not self.is_new() and self._doc_before_save:
_warehouse_before_save = {
d.name: d.warehouse for d in self._doc_before_save.get("reorder_levels") or []
}
for d in self.get("reorder_levels"):
if not d.warehouse_group:
d.warehouse_group = d.warehouse
@@ -413,6 +420,19 @@ class Item(Document):
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
if d.warehouse_group and d.warehouse:
if _warehouse_before_save.get(d.name) == d.warehouse:
continue
child_warehouses = get_child_warehouses(d.warehouse_group)
if d.warehouse not in child_warehouses:
frappe.throw(
_(
"Row #{0}: The warehouse {1} is not a child warehouse of a group warehouse {2}"
).format(d.idx, bold(d.warehouse), bold(d.warehouse_group)),
title=_("Incorrect Check in (group) Warehouse for Reorder"),
)
def stock_ledger_created(self):
if not hasattr(self, "_stock_ledger_created"):
self._stock_ledger_created = len(
@@ -1318,3 +1338,10 @@ def get_asset_naming_series():
from erpnext.assets.doctype.asset.asset import get_asset_naming_series
return get_asset_naming_series()
@frappe.request_cache
def get_child_warehouses(warehouse):
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
return get_child_warehouses(warehouse)

View File

@@ -848,6 +848,27 @@ class TestItem(FrappeTestCase):
self.assertEqual(data[0].description, item.description)
self.assertTrue("description" in data[0])
def test_group_warehouse_for_reorder_item(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
item_doc = make_item("_Test Group Warehouse For Reorder Item", {"is_stock_item": 1})
warehouse = create_warehouse("_Test Warehouse - _TC")
warehouse_doc = frappe.get_doc("Warehouse", warehouse)
warehouse_doc.db_set("parent_warehouse", "")
item_doc.append(
"reorder_levels",
{
"warehouse": warehouse,
"warehouse_reorder_level": 10,
"warehouse_reorder_qty": 100,
"material_request_type": "Purchase",
"warehouse_group": "_Test Warehouse Group - _TC",
},
)
self.assertRaises(frappe.ValidationError, item_doc.save)
def set_item_variant_settings(fields):
doc = frappe.get_doc("Item Variant Settings")

View File

@@ -224,7 +224,10 @@ class LandedCostVoucher(Document):
# update stock & gl entries for submit state of PR
doc.docstatus = 1
doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
doc.make_gl_entries()
if d.receipt_document_type == "Purchase Receipt":
doc.make_gl_entries(via_landed_cost_voucher=True)
else:
doc.make_gl_entries()
doc.repost_future_sle_and_gle()
def validate_asset_qty_and_status(self, receipt_document_type, receipt_document):

View File

@@ -18,6 +18,7 @@
"parent_warehouse",
"consider_rejected_warehouses",
"get_item_locations",
"pick_manually",
"section_break_6",
"scan_barcode",
"column_break_13",
@@ -192,11 +193,18 @@
"fieldname": "consider_rejected_warehouses",
"fieldtype": "Check",
"label": "Consider Rejected Warehouses"
},
{
"default": "0",
"description": "If enabled then system won't override the picked qty / batches / serial numbers.",
"fieldname": "pick_manually",
"fieldtype": "Check",
"label": "Pick Manually"
}
],
"is_submittable": 1,
"links": [],
"modified": "2024-01-24 17:05:20.317180",
"modified": "2024-03-27 22:49:16.954637",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
@@ -268,4 +276,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -29,7 +29,8 @@ class PickList(Document):
def before_save(self):
self.update_status()
self.set_item_locations()
if not self.pick_manually:
self.set_item_locations()
if self.get("locations"):
self.validate_sales_order_percentage()

View File

@@ -307,13 +307,13 @@ class PurchaseReceipt(BuyingController):
self.delete_auto_created_batches()
self.set_consumed_qty_in_subcontract_order()
def get_gl_entries(self, warehouse_account=None):
def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False):
from erpnext.accounts.general_ledger import process_gl_map
gl_entries = []
self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account)
self.make_tax_gl_entries(gl_entries)
self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher)
update_regional_gl_entries(gl_entries, self)
return process_gl_map(gl_entries)
@@ -661,7 +661,7 @@ class PurchaseReceipt(BuyingController):
posting_date=posting_date,
)
def make_tax_gl_entries(self, gl_entries):
def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False):
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")])
is_asset_pr = any(d.is_fixed_asset for d in self.get("items"))
# Cost center-wise amount breakup for other charges included for valuation
@@ -696,18 +696,17 @@ class PurchaseReceipt(BuyingController):
i = 1
for tax in self.get("taxes"):
if valuation_tax.get(tax.name):
negative_expense_booked_in_pi = frappe.db.sql(
"""select name from `tabPurchase Invoice Item` pi
where docstatus = 1 and purchase_receipt=%s
and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice'
and voucher_no=pi.parent and account=%s)""",
(self.name, tax.account_head),
)
if negative_expense_booked_in_pi:
account = stock_rbnb
else:
if via_landed_cost_voucher:
account = tax.account_head
else:
negative_expense_booked_in_pi = frappe.db.sql(
"""select name from `tabPurchase Invoice Item` pi
where docstatus = 1 and purchase_receipt=%s
and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice'
and voucher_no=pi.parent and account=%s)""",
(self.name, tax.account_head),
)
account = stock_rbnb if negative_expense_booked_in_pi else tax.account_head
if i == len(valuation_tax):
applicable_amount = amount_including_divisional_loss

View File

@@ -9,6 +9,7 @@ from pypika import functions as fn
import erpnext
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.controllers.buying_controller import QtyMismatchError
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -1640,7 +1641,6 @@ class TestPurchaseReceipt(FrappeTestCase):
frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0)
def test_internal_pr_gl_entries(self):
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -2301,6 +2301,54 @@ class TestPurchaseReceipt(FrappeTestCase):
for index, d in enumerate(data):
self.assertEqual(d.qty_after_transaction, 11 + index)
def test_valuation_taxes_lcv_repost_after_billing(self):
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
make_landed_cost_voucher,
)
old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company")
frappe.local.enable_perpetual_inventory["_Test Company"] = 1
frappe.db.set_value(
"Company",
"_Test Company",
"stock_received_but_not_billed",
"Stock Received But Not Billed - _TC",
)
pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1)
pr.append(
"taxes",
{
"category": "Valuation and Total",
"charge_type": "Actual",
"account_head": "Freight and Forwarding Charges - _TC",
"tax_amount": 2000,
"description": "Test",
},
)
pr.submit()
pi = make_purchase_invoice(pr.name)
pi.submit()
make_landed_cost_voucher(
company=pr.company,
receipt_document_type="Purchase Receipt",
receipt_document=pr.name,
charges=2000,
distribute_charges_based_on="Qty",
expense_account="Expenses Included In Valuation - _TC",
)
gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False)
warehouse_account = get_warehouse_account_map("_Test Company")
expected_gle = (
("Stock Received But Not Billed - _TC", 0, 10000, "Main - _TC"),
("Freight and Forwarding Charges - _TC", 0, 2000, "Main - _TC"),
("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"),
(warehouse_account[pr.items[0].warehouse]["account"], 14000, 0, "Main - _TC"),
)
self.assertSequenceEqual(expected_gle, gl_entries)
frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
@@ -2347,14 +2395,24 @@ def get_sl_entries(voucher_type, voucher_no):
)
def get_gl_entries(voucher_type, voucher_no):
return frappe.db.sql(
"""select account, debit, credit, cost_center, is_cancelled
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
order by account desc""",
(voucher_type, voucher_no),
as_dict=1,
def get_gl_entries(voucher_type, voucher_no, skip_cancelled=False, as_dict=True):
gl = frappe.qb.DocType("GL Entry")
gl_query = (
frappe.qb.from_(gl)
.select(
gl.account,
gl.debit,
gl.credit,
gl.cost_center,
)
.where((gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no))
.orderby(gl.account, order=frappe.qb.desc)
)
if skip_cancelled:
gl_query = gl_query.where(gl.is_cancelled == 0)
else:
gl_query = gl_query.select(gl.is_cancelled)
return gl_query.run(as_dict=as_dict)
def get_taxes(**args):

View File

@@ -45,7 +45,7 @@ class RepostItemValuation(Document):
def validate_period_closing_voucher(self):
# Period Closing Voucher
year_end_date = self.get_max_year_end_date(self.company)
year_end_date = self.get_max_period_closing_date(self.company)
if year_end_date and getdate(self.posting_date) <= getdate(year_end_date):
date = frappe.format(year_end_date, "Date")
msg = f"Due to period closing, you cannot repost item valuation before {date}"
@@ -88,24 +88,16 @@ class RepostItemValuation(Document):
return frappe.get_all("Closing Stock Balance", fields=["name", "to_date"], filters=filters)
@staticmethod
def get_max_year_end_date(company):
data = frappe.get_all(
"Period Closing Voucher", fields=["fiscal_year"], filters={"docstatus": 1, "company": company}
)
if not data:
return
fiscal_years = [d.fiscal_year for d in data]
table = frappe.qb.DocType("Fiscal Year")
def get_max_period_closing_date(company):
table = frappe.qb.DocType("Period Closing Voucher")
query = (
frappe.qb.from_(table)
.select(Max(table.year_end_date))
.where((table.name.isin(fiscal_years)) & (table.disabled == 0))
.select(Max(table.posting_date))
.where((table.company == company) & (table.docstatus == 1))
).run()
return query[0][0] if query else None
return query[0][0] if query and query[0][0] else None
def validate_accounts_freeze(self):
acc_settings = frappe.db.get_value(

Some files were not shown because too many files have changed in this diff Show More