Merge pull request #37545 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
Deepesh Garg
2023-10-19 17:04:34 +05:30
committed by GitHub
46 changed files with 604 additions and 149 deletions

View File

@@ -37,6 +37,7 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date):
}
)
cle.flags.ignore_permissions = True
cle.flags.ignore_links = True
cle.submit()

View File

@@ -301,3 +301,30 @@ def get_dimensions(with_cost_center_and_project=False):
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
return dimension_filters, default_dimensions_map
def create_accounting_dimensions_for_doctype(doctype):
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
for d in accounting_dimensions:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": "accounting_dimensions_section",
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)

View File

@@ -181,6 +181,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
}
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
}
unblock_invoice() {

View File

@@ -36,6 +36,7 @@
"currency_and_price_list",
"currency",
"conversion_rate",
"use_transaction_date_exchange_rate",
"column_break2",
"buying_price_list",
"price_list_currency",
@@ -1578,13 +1579,20 @@
"label": "Repost Required",
"options": "Account",
"read_only": 1
},
{
"default": "0",
"fieldname": "use_transaction_date_exchange_rate",
"fieldtype": "Check",
"label": "Use Transaction Date Exchange Rate",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2023-10-01 21:01:47.282533",
"modified": "2023-10-16 16:24:51.886231",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -546,8 +546,9 @@ class PurchaseInvoice(BuyingController):
]
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
def make_gl_entries(self, gl_entries=None, from_repost=False):
if not gl_entries:

View File

@@ -5,7 +5,7 @@
import unittest
import frappe
from frappe.tests.utils import change_settings
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
import erpnext
@@ -33,7 +33,7 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ
test_ignore = ["Serial No"]
class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
@classmethod
def setUpClass(self):
unlink_payment_on_cancel_of_invoice()
@@ -43,6 +43,9 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
def tearDownClass(self):
unlink_payment_on_cancel_of_invoice(0)
def tearDown(self):
frappe.db.rollback()
def test_purchase_invoice_received_qty(self):
"""
1. Test if received qty is validated against accepted + rejected
@@ -417,6 +420,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertEqual(tax.tax_amount, expected_values[i][1])
self.assertEqual(tax.total, expected_values[i][2])
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_purchase_invoice_with_advance(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -471,6 +475,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
)
)
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_invoice_with_advance_and_multi_payment_terms(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -1209,6 +1214,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save()
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_gain_loss_with_advance_entry(self):
unlink_enabled = frappe.db.get_value(
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
@@ -1411,6 +1417,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
)
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_purchase_invoice_advance_taxes(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry

View File

@@ -548,8 +548,9 @@ class SalesInvoice(SellingController):
"taxes": ("account_head",),
}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
def set_paid_amount(self):
paid_amount = 0.0

View File

@@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.model.naming import make_autoname
from frappe.tests.utils import change_settings
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
import erpnext
@@ -38,13 +38,17 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
class TestSalesInvoice(unittest.TestCase):
class TestSalesInvoice(FrappeTestCase):
def setUp(self):
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
create_internal_parties()
setup_accounts()
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def tearDown(self):
frappe.db.rollback()
def make(self):
w = frappe.copy_doc(test_records[0])
@@ -172,6 +176,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(frappe.LinkExistsError, si.cancel)
unlink_payment_on_cancel_of_invoice()
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_payment_entry_unlink_against_standalone_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
@@ -1293,6 +1298,7 @@ class TestSalesInvoice(unittest.TestCase):
dn.submit()
return dn
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_sales_invoice_with_advance(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
test_records as jv_test_records,
@@ -2774,6 +2780,13 @@ class TestSalesInvoice(unittest.TestCase):
company="_Test Company",
)
tds_payable_account = create_account(
account_name="TDS Payable",
account_type="Tax",
parent_account="Duties and Taxes - _TC",
company="_Test Company",
)
si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1)
si.apply_discount_on = "Grand Total"
si.additional_discount_account = additional_discount_account
@@ -3072,8 +3085,8 @@ class TestSalesInvoice(unittest.TestCase):
si.commission_rate = commission_rate
self.assertRaises(frappe.ValidationError, si.save)
@change_settings("Accounts Settings", {"acc_frozen_upto": add_days(getdate(), 1)})
def test_sales_invoice_submission_post_account_freezing_date(self):
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", add_days(getdate(), 1))
si = create_sales_invoice(do_not_save=True)
si.posting_date = add_days(getdate(), 1)
si.save()
@@ -3082,8 +3095,6 @@ class TestSalesInvoice(unittest.TestCase):
si.posting_date = getdate()
si.submit()
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
def test_over_billing_case_against_delivery_note(self):
"""
Test a case where duplicating the item with qty = 1 in the invoice
@@ -3112,6 +3123,13 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", over_billing_allowance)
@change_settings(
"Accounts Settings",
{
"book_deferred_entries_via_journal_entry": 1,
"submit_journal_entries": 1,
},
)
def test_multi_currency_deferred_revenue_via_journal_entry(self):
deferred_account = create_account(
account_name="Deferred Revenue",
@@ -3119,11 +3137,6 @@ class TestSalesInvoice(unittest.TestCase):
company="_Test Company",
)
acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 1
acc_settings.submit_journal_entries = 1
acc_settings.save()
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_expense = 1
item.item_defaults[0].deferred_revenue_account = deferred_account
@@ -3189,13 +3202,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][2], gle.debit)
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entries = 0
acc_settings.save()
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
def test_standalone_serial_no_return(self):
si = create_sales_invoice(
item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1

View File

@@ -4,6 +4,7 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils.data import (
add_days,
add_months,
@@ -90,10 +91,14 @@ def create_parties():
customer.insert()
class TestSubscription(unittest.TestCase):
class TestSubscription(FrappeTestCase):
def setUp(self):
create_plan()
create_parties()
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def tearDown(self):
frappe.db.rollback()
def test_create_subscription_with_trial_with_correct_period(self):
subscription = frappe.new_doc("Subscription")

View File

@@ -6,12 +6,7 @@ from typing import Optional
import frappe
from frappe import _, msgprint, scrub
from frappe.contacts.doctype.address.address import (
get_address_display,
get_company_address,
get_default_address,
)
from frappe.contacts.doctype.contact.contact import get_contact_details
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import Abs, Date, Sum
@@ -133,6 +128,7 @@ def _get_party_details(
party_address,
company_address,
shipping_address,
ignore_permissions=ignore_permissions,
)
set_contact_details(party_details, party, party_type)
set_other_values(party_details, party, party_type)
@@ -193,6 +189,8 @@ def set_address_details(
party_address=None,
company_address=None,
shipping_address=None,
*,
ignore_permissions=False
):
billing_address_field = (
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -205,13 +203,17 @@ def set_address_details(
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
)
# address display
party_details.address_display = get_address_display(party_details[billing_address_field])
party_details.address_display = render_address(
party_details[billing_address_field], check_permissions=not ignore_permissions
)
# shipping address
if party_type in ["Customer", "Lead"]:
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
party_type, party.name
)
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
party_details.shipping_address = render_address(
party_details["shipping_address_name"], check_permissions=not ignore_permissions
)
if doctype:
party_details.update(
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
@@ -229,7 +231,7 @@ def set_address_details(
if shipping_address:
party_details.update(
shipping_address=shipping_address,
shipping_address_display=get_address_display(shipping_address),
shipping_address_display=render_address(shipping_address),
**get_fetch_values(doctype, "shipping_address", shipping_address)
)
@@ -238,7 +240,8 @@ def set_address_details(
party_details.update(
billing_address=party_details.company_address,
billing_address_display=(
party_details.company_address_display or get_address_display(party_details.company_address)
party_details.company_address_display
or render_address(party_details.company_address, check_permissions=False)
),
**get_fetch_values(doctype, "billing_address", party_details.company_address)
)
@@ -290,7 +293,34 @@ def set_contact_details(party_details, party, party_type):
}
)
else:
party_details.update(get_contact_details(party_details.contact_person))
fields = [
"name as contact_person",
"salutation",
"first_name",
"last_name",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
]
contact_details = frappe.db.get_value(
"Contact", party_details.contact_person, fields, as_dict=True
)
contact_details.contact_display = " ".join(
filter(
None,
[
contact_details.get("salutation"),
contact_details.get("first_name"),
contact_details.get("last_name"),
],
)
)
party_details.update(contact_details)
def set_other_values(party_details, party, party_type):
@@ -957,3 +987,13 @@ def add_party_account(party_type, party, company, account):
doc.append("accounts", accounts)
doc.save()
def render_address(address, check_permissions=True):
try:
from frappe.contacts.doctype.address.address import render_address as _render
except ImportError:
# Older frappe versions where this function is not available
from frappe.contacts.doctype.address.address import get_address_display as _render
return frappe.call(_render, address, check_permissions=check_permissions)

View File

@@ -116,7 +116,7 @@ class ReceivablePayableReport(object):
# build all keys, since we want to exclude vouchers beyond the report date
for ple in self.ple_entries:
# get the balance object for voucher_type
key = (ple.voucher_type, ple.voucher_no, ple.party)
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
if not key in self.voucher_balance:
self.voucher_balance[key] = frappe._dict(
voucher_type=ple.voucher_type,
@@ -183,7 +183,7 @@ class ReceivablePayableReport(object):
):
return
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
# If payment is made against credit note
# and credit note is made against a Sales Invoice
@@ -192,13 +192,13 @@ class ReceivablePayableReport(object):
if ple.against_voucher_no in self.return_entries:
return_against = self.return_entries.get(ple.against_voucher_no)
if return_against:
key = (ple.against_voucher_type, return_against, ple.party)
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
row = self.voucher_balance.get(key)
if not row:
# no invoice, this is an invoice / stand-alone payment / credit note
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
row.party_type = ple.party_type
return row

View File

@@ -1,6 +1,7 @@
import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, today
@@ -23,29 +24,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def create_usd_account(self):
name = "Debtors USD"
exists = frappe.db.get_list(
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"}
)
if exists:
self.debtors_usd = exists[0].name
else:
debtors = frappe.get_doc(
"Account",
frappe.db.get_list(
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors"}
)[0].name,
)
debtors_usd = frappe.new_doc("Account")
debtors_usd.company = debtors.company
debtors_usd.account_name = "Debtors USD"
debtors_usd.account_currency = "USD"
debtors_usd.parent_account = debtors.parent_account
debtors_usd.account_type = debtors.account_type
self.debtors_usd = debtors_usd.save().name
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
frappe.set_user("Administrator")
si = create_sales_invoice(
@@ -643,3 +621,94 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
self.assertEqual(len(report[1]), 2)
output_for = set([x.party for x in report[1]])
self.assertEqual(output_for, expected_output)
def test_report_output_if_party_is_missing(self):
acc_name = "Additional Debtors"
if not frappe.db.get_value(
"Account", filters={"account_name": acc_name, "company": self.company}
):
additional_receivable_acc = frappe.get_doc(
{
"doctype": "Account",
"account_name": acc_name,
"parent_account": "Accounts Receivable - " + self.company_abbr,
"company": self.company,
"account_type": "Receivable",
}
).save()
self.debtors2 = additional_receivable_acc.name
je = frappe.new_doc("Journal Entry")
je.company = self.company
je.posting_date = today()
je.append(
"accounts",
{
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"debit_in_account_currency": 150,
"credit_in_account_currency": 0,
"cost_center": self.cost_center,
},
)
je.append(
"accounts",
{
"account": self.debtors2,
"party_type": "Customer",
"party": self.customer,
"debit_in_account_currency": 200,
"credit_in_account_currency": 0,
"cost_center": self.cost_center,
},
)
je.append(
"accounts",
{
"account": self.cash,
"debit_in_account_currency": 0,
"credit_in_account_currency": 350,
"cost_center": self.cost_center,
},
)
je.save().submit()
# manually remove party from Payment Ledger
ple = qb.DocType("Payment Ledger Entry")
qb.update(ple).set(ple.party, None).where(ple.voucher_no == je.name).run()
filters = {
"company": self.company,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
report_ouput = execute(filters)[1]
expected_data = [
[self.debtors2, je.doctype, je.name, "Customer", self.customer, 200.0, 0.0, 0.0, 200.0],
[self.debit_to, je.doctype, je.name, "Customer", self.customer, 150.0, 0.0, 0.0, 150.0],
]
self.assertEqual(len(report_ouput), 2)
# fetch only required fields
report_output = [
[
x.party_account,
x.voucher_type,
x.voucher_no,
"Customer",
self.customer,
x.invoiced,
x.paid,
x.credit_note,
x.outstanding,
]
for x in report_ouput
]
# use account name to sort
# post sorting output should be [[Additional Debtors, ...], [Debtors, ...]]
report_output = sorted(report_output, key=lambda x: x[0])
self.assertEqual(expected_data, report_output)

View File

@@ -23,6 +23,7 @@ class TestBankReconciliationStatement(FrappeTestCase):
"Payment Entry",
]:
frappe.db.delete(dt)
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
def test_loan_entries_in_bank_reco_statement(self):
create_loan_accounts()

View File

@@ -133,15 +133,17 @@ class General_Payment_Ledger_Comparison(object):
self.gle_balances = set(val.gle) | self.gle_balances
self.ple_balances = set(val.ple) | self.ple_balances
self.diff1 = self.gle_balances.difference(self.ple_balances)
self.diff2 = self.ple_balances.difference(self.gle_balances)
self.variation_in_payment_ledger = self.gle_balances.difference(self.ple_balances)
self.variation_in_general_ledger = self.ple_balances.difference(self.gle_balances)
self.diff = frappe._dict({})
for x in self.diff1:
for x in self.variation_in_payment_ledger:
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
for x in self.diff2:
self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]}))
for x in self.variation_in_general_ledger:
self.diff.setdefault((x[0], x[1], x[2], x[3]), frappe._dict({"gl_balance": 0.0})).update(
frappe._dict({"pl_balance": x[4]})
)
def generate_data(self):
self.data = []

View File

@@ -544,6 +544,8 @@ class GrossProfitGenerator(object):
new_row.qty += flt(row.qty)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
new_row.base_amount += flt(row.base_amount, self.currency_precision)
if self.filters.get("group_by") == "Sales Person":
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)

View File

@@ -68,7 +68,11 @@ def get_result(
tax_amount += entry.credit - entry.debit
if net_total_map.get(name):
total_amount, grand_total, base_total = net_total_map.get(name)
if voucher_type == "Journal Entry":
# back calcalute total amount from rate and tax_amount
total_amount = grand_total = base_total = tax_amount / (rate / 100)
else:
total_amount, grand_total, base_total = net_total_map.get(name)
else:
total_amount += entry.credit

View File

@@ -284,7 +284,7 @@ frappe.ui.form.on('Asset', {
item_code: function(frm) {
if(frm.doc.item_code && frm.doc.calculate_depreciation) {
if(frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
frm.trigger('set_finance_book');
} else {
frm.set_value('finance_books', []);
@@ -448,7 +448,7 @@ frappe.ui.form.on('Asset', {
calculate_depreciation: function(frm) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
if (frm.doc.item_code && frm.doc.calculate_depreciation ) {
if (frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
frm.trigger("set_finance_book");
} else {
frm.set_value("finance_books", []);

View File

@@ -24,6 +24,7 @@
"bill_for_rejected_quantity_in_purchase_invoice",
"disable_last_purchase_rate",
"show_pay_button",
"use_transaction_date_exchange_rate",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
@@ -164,6 +165,13 @@
"fieldname": "over_order_allowance",
"fieldtype": "Float",
"label": "Over Order Allowance (%)"
},
{
"default": "0",
"description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.",
"fieldname": "use_transaction_date_exchange_rate",
"fieldtype": "Check",
"label": "Use Transaction Date Exchange Rate"
}
],
"icon": "fa fa-cog",
@@ -171,7 +179,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-03-02 17:02:14.404622",
"modified": "2023-10-16 16:22:03.201078",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -8,7 +8,7 @@ def get_data():
"This is based on transactions against this Supplier. See timeline below for details"
),
"fieldname": "supplier",
"non_standard_fieldnames": {"Payment Entry": "party_name", "Bank Account": "party"},
"non_standard_fieldnames": {"Payment Entry": "party", "Bank Account": "party"},
"transactions": [
{"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]},
{"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]},

View File

@@ -6,7 +6,7 @@ import copy
import frappe
from frappe import _
from frappe.query_builder.functions import IfNull
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import date_diff, flt, getdate
@@ -57,7 +57,7 @@ def get_data(filters):
po_item.qty,
po_item.received_qty,
(po_item.qty - po_item.received_qty).as_("pending_qty"),
IfNull(pi_item.qty, 0).as_("billed_qty"),
Sum(IfNull(pi_item.qty, 0)).as_("billed_qty"),
po_item.base_amount.as_("amount"),
(po_item.received_qty * po_item.base_rate).as_("received_qty_amount"),
(po_item.billed_amt * IfNull(po.conversion_rate, 1)).as_("billed_amount"),

View File

@@ -12,6 +12,7 @@ from frappe.utils import (
add_days,
add_months,
cint,
comma_and,
flt,
fmt_money,
formatdate,
@@ -180,6 +181,17 @@ class AccountsController(TransactionBase):
self.validate_party_account_currency()
if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
if invalid_advances := [
x for x in self.advances if not x.reference_type or not x.reference_name
]:
frappe.throw(
_(
"Rows: {0} in {1} section are Invalid. Reference Name should point to a valid Payment Entry or Journal Entry."
).format(
frappe.bold(comma_and([x.idx for x in invalid_advances])), frappe.bold(_("Advance Payments"))
)
)
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()
@@ -571,6 +583,17 @@ class AccountsController(TransactionBase):
self.currency, self.company_currency, transaction_date, args
)
if (
self.currency
and buying_or_selling == "Buying"
and frappe.db.get_single_value("Buying Settings", "use_transaction_date_exchange_rate")
and self.doctype == "Purchase Invoice"
):
self.use_transaction_date_exchange_rate = True
self.conversion_rate = get_exchange_rate(
self.currency, self.company_currency, transaction_date, args
)
def set_missing_item_details(self, for_validate=False):
"""set missing item values"""
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -594,6 +617,7 @@ class AccountsController(TransactionBase):
self.pricing_rules = []
selected_serial_nos_map = {}
for item in self.get("items"):
if item.get("item_code"):
args = parent_dict.copy()
@@ -605,6 +629,7 @@ class AccountsController(TransactionBase):
args["ignore_pricing_rule"] = (
self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0
)
args["ignore_serial_nos"] = selected_serial_nos_map.get(item.get("item_code"))
if not args.get("transaction_date"):
args["transaction_date"] = args.get("posting_date")
@@ -661,6 +686,11 @@ class AccountsController(TransactionBase):
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
self.set_pricing_rule_details(item, ret)
if ret.get("serial_no"):
selected_serial_nos_map.setdefault(item.get("item_code"), []).extend(
ret.get("serial_no").split("\n")
)
else:
# Transactions line item without item code

View File

@@ -4,9 +4,9 @@
import frappe
from frappe import _, bold, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.utils import cint, cstr, flt, get_link_to_form, nowtime
from erpnext.accounts.party import render_address
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController
@@ -591,7 +591,9 @@ class SellingController(StockController):
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], []

View File

@@ -517,6 +517,7 @@ accounting_dimension_doctypes = [
"Sales Invoice Item",
"Purchase Invoice Item",
"Purchase Order Item",
"Sales Order Item",
"Journal Entry Account",
"Material Request Item",
"Delivery Note Item",

View File

@@ -341,5 +341,6 @@ execute:frappe.defaults.clear_default("fiscal_year")
execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0)
erpnext.patches.v14_0.correct_asset_value_if_je_with_workflow
erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults
erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger

View File

@@ -0,0 +1,7 @@
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
create_accounting_dimensions_for_doctype,
)
def execute():
create_accounting_dimensions_for_doctype(doctype="Sales Order Item")

View File

@@ -24,6 +24,7 @@
},
{
"fetch_from": "task.subject",
"fetch_if_empty": 1,
"fieldname": "subject",
"fieldtype": "Text",
"in_list_view": 1,
@@ -31,7 +32,6 @@
"read_only": 1
},
{
"fetch_from": "task.project",
"fieldname": "project",
"fieldtype": "Text",
"label": "Project",
@@ -40,7 +40,7 @@
],
"istable": 1,
"links": [],
"modified": "2023-10-09 11:34:14.335853",
"modified": "2023-10-17 12:45:21.536165",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task Depends On",

View File

@@ -19,7 +19,7 @@ erpnext.accounts.unreconcile_payments = {
if (r.message) {
frm.add_custom_button(__("Un-Reconcile"), function() {
erpnext.accounts.unreconcile_payments.build_unreconcile_dialog(frm);
});
}, __('Actions'));
}
}
});

View File

@@ -177,7 +177,8 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype):
"parent": invoice.name,
"item_tax_template": vat_setting.item_tax_template,
},
fields=["item_code", "base_net_amount"],
fields=["item_code", "sum(base_net_amount) as base_net_amount"],
group_by="item_code, item_tax_template",
)
for item in invoice_items:

View File

@@ -542,29 +542,37 @@ def close_or_unclose_sales_orders(names, status):
def get_requested_item_qty(sales_order):
return frappe._dict(
frappe.db.sql(
"""
select sales_order_item, sum(qty)
from `tabMaterial Request Item`
where docstatus = 1
and sales_order = %s
group by sales_order_item
""",
sales_order,
)
)
result = {}
for d in frappe.db.get_all(
"Material Request Item",
filters={"docstatus": 1, "sales_order": sales_order},
fields=["sales_order_item", "sum(qty) as qty", "sum(received_qty) as received_qty"],
group_by="sales_order_item",
):
result[d.sales_order_item] = frappe._dict({"qty": d.qty, "received_qty": d.received_qty})
return result
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
requested_item_qty = get_requested_item_qty(source_name)
def get_remaining_qty(so_item):
return flt(
flt(so_item.qty)
- flt(requested_item_qty.get(so_item.name, {}).get("qty"))
- max(
flt(so_item.get("delivered_qty"))
- flt(requested_item_qty.get(so_item.name, {}).get("received_qty")),
0,
)
)
def update_item(source, target, source_parent):
# qty is for packed items, because packed items don't have stock_qty field
qty = source.get("qty")
target.project = source_parent.project
target.qty = qty - requested_item_qty.get(source.name, 0) - flt(source.get("delivered_qty"))
target.qty = get_remaining_qty(source)
target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
args = target.as_dict().copy()
@@ -597,8 +605,8 @@ def make_material_request(source_name, target_doc=None):
"Sales Order Item": {
"doctype": "Material Request Item",
"field_map": {"name": "sales_order_item", "parent": "sales_order"},
"condition": lambda doc: not frappe.db.exists("Product Bundle", doc.item_code)
and (doc.stock_qty - flt(doc.get("delivered_qty"))) > requested_item_qty.get(doc.name, 0),
"condition": lambda item: not frappe.db.exists("Product Bundle", item.item_code)
and get_remaining_qty(item) > 0,
"postprocess": update_item,
},
},

View File

@@ -66,6 +66,7 @@
"total_weight",
"column_break_21",
"weight_uom",
"accounting_dimensions_section",
"warehouse_and_reference",
"warehouse",
"target_warehouse",
@@ -868,12 +869,18 @@
"label": "Production Plan Qty",
"no_copy": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-07-28 14:56:42.031636",
"modified": "2023-10-17 18:18:26.475259",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
@@ -884,4 +891,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -33,6 +33,7 @@ def after_install():
add_app_name()
setup_log_settings()
hide_workspaces()
update_roles()
frappe.db.commit()
@@ -214,6 +215,12 @@ def hide_workspaces():
frappe.db.set_value("Workspace", ws, "public", 0)
def update_roles():
website_user_roles = ("Customer", "Supplier")
for role in website_user_roles:
frappe.db.set_value("Role", role, "desk_access", 0)
def create_default_role_profiles():
for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items():
role_profile = frappe.new_doc("Role Profile")

View File

@@ -144,6 +144,7 @@ class DeliveryNote(SellingController):
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
self.set_product_bundle_reference_in_packed_items() # should be called before `make_packing_list`
make_packing_list(self)
if self._action != "submit" and not self.is_return:
@@ -430,6 +431,17 @@ class DeliveryNote(SellingController):
else:
serial_nos.append(serial_no)
def set_product_bundle_reference_in_packed_items(self):
if self.packed_items and ((self.is_return and self.return_against) or self.amended_from):
if items_ref_map := {
item.dn_detail or item.get("_amended_from"): item.name
for item in self.items
if item.dn_detail or item.get("_amended_from")
}:
for item in self.packed_items:
if item.parent_detail_docname in items_ref_map:
item.parent_detail_docname = items_ref_map[item.parent_detail_docname]
def update_billed_amount_based_on_so(so_detail, update_modified=True):
from frappe.query_builder.functions import Sum

View File

@@ -5,11 +5,12 @@
import json
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, cstr, flt, nowdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.utils import get_balance_on
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
@@ -268,8 +269,6 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(dn.items[0].returned_qty, 2)
self.assertEqual(dn.per_returned, 40)
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return_dn_2 = make_return_doc("Delivery Note", dn.name)
# Check if unreturned amount is mapped in 2nd return
@@ -361,8 +360,6 @@ class TestDeliveryNote(FrappeTestCase):
dn.submit()
self.assertEqual(dn.items[0].incoming_rate, 150)
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return_dn = make_return_doc(dn.doctype, dn.name)
return_dn.items[0].warehouse = return_warehouse
return_dn.save().submit()
@@ -1182,7 +1179,6 @@ class TestDeliveryNote(FrappeTestCase):
)
def test_batch_expiry_for_delivery_note(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
item = make_item(
@@ -1239,6 +1235,82 @@ class TestDeliveryNote(FrappeTestCase):
# Test - 1: ValidationError should be raised
self.assertRaises(frappe.ValidationError, dn.submit)
def test_packed_items_for_return_delivery_note(self):
# Step - 1: Create Items
product_bundle_item = make_item(properties={"is_stock_item": 0}).name
batch_item = make_item(
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TEST-BATCH-.#####",
}
).name
serial_item = make_item(
properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "TEST-SERIAL-.#####"}
).name
# Step - 2: Inward Stock
se1 = make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=3)
serial_nos = (
make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=3)
.items[0]
.serial_no
)
# Step - 3: Create a Product Bundle
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import (
create_product_bundle_item,
)
create_product_bundle_item(product_bundle_item, packed_items=[[batch_item, 1], [serial_item, 1]])
# Step - 4: Create a Delivery Note for the Product Bundle
dn = create_delivery_note(
item_code=product_bundle_item,
warehouse="_Test Warehouse - _TC",
qty=3,
do_not_submit=True,
)
dn.packed_items[1].serial_no = serial_nos
dn.save()
dn.submit()
# Step - 5: Create a Return Delivery Note(Sales Return)
return_dn = make_return_doc(dn.doctype, dn.name)
return_dn.save()
return_dn.submit()
self.assertEqual(return_dn.packed_items[0].batch_no, dn.packed_items[0].batch_no)
self.assertEqual(return_dn.packed_items[1].serial_no, dn.packed_items[1].serial_no)
@change_settings("Stock Settings", {"automatically_set_serial_nos_based_on_fifo": 1})
def test_delivery_note_for_repetitive_serial_item(self):
# Step - 1: Create Serial Item
item, warehouse = (
make_item(
properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "TEST-SERIAL-.###"}
).name,
"_Test Warehouse - _TC",
)
# Step - 2: Inward Stock
make_stock_entry(item_code=item, target=warehouse, qty=5)
# Step - 3: Create Delivery Note with repetitive Serial Item
dn = create_delivery_note(item_code=item, warehouse=warehouse, qty=2, do_not_save=True)
dn.append("items", dn.items[0].as_dict())
dn.items[1].qty = 3
dn.save()
dn.submit()
# Test - 1: Serial Nos should be different for each line item
serial_nos = []
for item in dn.items:
for serial_no in item.serial_no.split("\n"):
self.assertNotIn(serial_no, serial_nos)
serial_nos.append(serial_no)
def tearDown(self):
frappe.db.rollback()
frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)

View File

@@ -741,7 +741,8 @@
"label": "Against Delivery Note Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "stock_qty_sec_break",
@@ -868,7 +869,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-07-25 11:58:28.101919",
"modified": "2023-10-16 16:18:18.013379",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",

View File

@@ -192,7 +192,6 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Parent Detail docname",
"no_copy": 1,
"oldfieldname": "parent_detail_docname",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -259,7 +258,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-04-28 13:16:38.460806",
"modified": "2023-10-14 23:26:11.755425",
"modified_by": "Administrator",
"module": "Stock",
"name": "Packed Item",

View File

@@ -341,7 +341,7 @@ class PurchaseReceipt(BuyingController):
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
for d in self.get("items"):
if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty):
if d.item_code in stock_items and flt(d.qty) and (flt(d.valuation_rate) or self.is_return):
if warehouse_account.get(d.warehouse):
stock_value_diff = frappe.db.get_value(
"Stock Ledger Entry",

View File

@@ -2147,6 +2147,62 @@ class TestPurchaseReceipt(FrappeTestCase):
# Test - 2: Valuation Rate should be equal to Previous SLE Valuation Rate
self.assertEqual(flt(sle.valuation_rate, 2), flt(previous_sle_valuation_rate, 2))
def test_purchase_return_with_zero_rate(self):
company = "_Test Company with perpetual inventory"
# Step - 1: Create Item
item, warehouse = (
make_item(properties={"is_stock_item": 1, "valuation_method": "Moving Average"}).name,
"Stores - TCP1",
)
# Step - 2: Create Stock Entry (Material Receipt)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
se = make_stock_entry(
purpose="Material Receipt",
item_code=item,
qty=100,
basic_rate=100,
to_warehouse=warehouse,
company=company,
)
# Step - 3: Create Purchase Receipt
pr = make_purchase_receipt(
item_code=item,
qty=5,
rate=0,
warehouse=warehouse,
company=company,
)
# Step - 4: Create Purchase Return
from erpnext.controllers.sales_and_purchase_return import make_return_doc
pr_return = make_return_doc("Purchase Receipt", pr.name)
pr_return.save()
pr_return.submit()
sl_entries = get_sl_entries(pr_return.doctype, pr_return.name)
gl_entries = get_gl_entries(pr_return.doctype, pr_return.name)
# Test - 1: SLE Stock Value Difference should be equal to Qty * Average Rate
average_rate = (
(se.items[0].qty * se.items[0].basic_rate) + (pr.items[0].qty * pr.items[0].rate)
) / (se.items[0].qty + pr.items[0].qty)
expected_stock_value_difference = pr_return.items[0].qty * average_rate
self.assertEqual(
flt(sl_entries[0].stock_value_difference, 2), flt(expected_stock_value_difference, 2)
)
# Test - 2: GL Entries should be created for Stock Value Difference
self.assertEqual(len(gl_entries), 2)
# Test - 3: SLE Stock Value Difference should be equal to Debit or Credit of GL Entries.
for entry in gl_entries:
self.assertEqual(abs(entry.debit + entry.credit), abs(sl_entries[0].stock_value_difference))
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -956,6 +956,58 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
self.assertRaises(frappe.ValidationError, sr.save)
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_backdated_stock_reco_for_batch_item_dont_have_future_sle(self):
# Step - 1: Create a Batch Item
from erpnext.stock.doctype.item.test_item import make_item
item = make_item(
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TEST-BATCH-.###",
}
).name
# Step - 2: Create Opening Stock Reconciliation
sr1 = create_stock_reconciliation(
item_code=item,
warehouse="_Test Warehouse - _TC",
qty=10,
purpose="Opening Stock",
posting_date=add_days(nowdate(), -2),
)
# Step - 3: Create Stock Entry (Material Receipt)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
se1 = make_stock_entry(
item_code=item,
target="_Test Warehouse - _TC",
qty=100,
)
# Step - 4: Create Stock Entry (Material Issue)
make_stock_entry(
item_code=item,
source="_Test Warehouse - _TC",
qty=100,
batch_no=se1.items[0].batch_no,
purpose="Material Issue",
)
# Step - 5: Create Stock Reconciliation (Backdated) after the Stock Reconciliation 1 (Step - 2)
sr2 = create_stock_reconciliation(
item_code=item,
warehouse="_Test Warehouse - _TC",
qty=5,
batch_no=sr1.items[0].batch_no,
posting_date=add_days(nowdate(), -1),
)
self.assertEqual(sr2.docstatus, 1)
def create_batch_item_with_batch(item_name, batch_id):
batch_item_doc = create_item(item_name, is_stock_item=1)

View File

@@ -160,6 +160,7 @@ def update_stock(args, out):
and out.warehouse
and out.stock_qty > 0
):
out["ignore_serial_nos"] = args.get("ignore_serial_nos")
if out.has_batch_no and not args.get("batch_no"):
out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty)
@@ -1140,6 +1141,8 @@ def get_serial_nos_by_fifo(args, sales_order=None):
query = query.where(sn.sales_order == sales_order)
if args.batch_no:
query = query.where(sn.batch_no == args.batch_no)
if args.ignore_serial_nos:
query = query.where(sn.name.notin(args.ignore_serial_nos))
serial_nos = query.run(as_list=True)
serial_nos = [s[0] for s in serial_nos]
@@ -1450,6 +1453,7 @@ def get_serial_no(args, serial_nos=None, sales_order=None):
"item_code": args.get("item_code"),
"warehouse": args.get("warehouse"),
"stock_qty": args.get("stock_qty"),
"ignore_serial_nos": args.get("ignore_serial_nos"),
}
)
args = process_args(args)

View File

@@ -699,7 +699,7 @@ class update_entries_after(object):
)
if self.valuation_method == "Moving Average":
rate = self.data[self.args.warehouse].previous_sle.valuation_rate
rate = flt(self.data[self.args.warehouse].previous_sle.valuation_rate)
else:
rate = get_rate_for_return(
sle.voucher_type,
@@ -1617,27 +1617,33 @@ def is_negative_with_precision(neg_sle, is_batch=False):
return qty_deficit < 0 and abs(qty_deficit) > 0.0001
def get_future_sle_with_negative_qty(args):
return frappe.db.sql(
"""
select
qty_after_transaction, posting_date, posting_time,
voucher_type, voucher_no
from `tabStock Ledger Entry`
where
item_code = %(item_code)s
and warehouse = %(warehouse)s
and voucher_no != %(voucher_no)s
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
and is_cancelled = 0
and qty_after_transaction < 0
order by timestamp(posting_date, posting_time) asc
limit 1
""",
args,
as_dict=1,
def get_future_sle_with_negative_qty(sle):
SLE = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(SLE)
.select(
SLE.qty_after_transaction, SLE.posting_date, SLE.posting_time, SLE.voucher_type, SLE.voucher_no
)
.where(
(SLE.item_code == sle.item_code)
& (SLE.warehouse == sle.warehouse)
& (SLE.voucher_no != sle.voucher_no)
& (
CombineDatetime(SLE.posting_date, SLE.posting_time)
>= CombineDatetime(sle.posting_date, sle.posting_time)
)
& (SLE.is_cancelled == 0)
& (SLE.qty_after_transaction < 0)
)
.orderby(CombineDatetime(SLE.posting_date, SLE.posting_time))
.limit(1)
)
if sle.voucher_type == "Stock Reconciliation" and sle.batch_no:
query = query.where(SLE.batch_no == sle.batch_no)
return query.run(as_dict=True)
def get_future_sle_with_negative_batch_qty(args):
return frappe.db.sql(

View File

@@ -7,7 +7,7 @@
{% if d.thumbnail or d.image %}
{{ product_image(d.thumbnail or d.image, no_border=True) }}
{% else %}
<div class="no-image-cart-item" style="min-height: 100px;">
<div class="no-image-cart-item" style="min-height: 50px;">
{{ frappe.utils.get_abbr(d.item_name) or "NA" }}
</div>
{% endif %}

View File

@@ -72,7 +72,7 @@ rfq = class rfq {
}
submit_rfq(){
$('.btn-sm').click(function(){
$('.btn-sm').click(function() {
frappe.freeze();
frappe.call({
type: "POST",
@@ -81,7 +81,7 @@ rfq = class rfq {
doc: doc
},
btn: this,
callback: function(r){
callback: function(r) {
frappe.unfreeze();
if(r.message){
$('.btn-sm').hide()

View File

@@ -1,19 +1,25 @@
{% from "erpnext/templates/includes/macros.html" import product_image_square, product_image %}
{% macro item_name_and_description(d, doc) %}
<div class="row">
<div class="col-3">
{{ product_image(d.image) }}
</div>
<div class="col-9">
{{ d.item_code }}
<p class="text-muted small">{{ d.description }}</p>
<div class="row">
<div class="col-3">
{% if d.image %}
{{ product_image(d.image) }}
{% else %}
<div class="website-image h-100 w-100" style="background-color:var(--gray-100);text-align: center;line-height: 3.6;">
{{ frappe.utils.get_abbr(d.item_name)}}
</div>
{% endif %}
</div>
<div class="col-9">
{{ d.item_code }}
<p class="text-muted small">{{ d.description }}</p>
{% set supplier_part_no = frappe.db.get_value("Item Supplier", {'parent': d.item_code, 'supplier': doc.supplier}, "supplier_part_no") %}
<p class="text-muted small supplier-part-no">
{% if supplier_part_no %}
{{_("Supplier Part No") + ": "+ supplier_part_no}}
{% endif %}
</p>
</div>
</div>
</div>
</div>
{% endmacro %}

View File

@@ -165,7 +165,6 @@
</div>
{% endif %}
{% if attachments %}
<div class="order-item-table">
<div class="row order-items order-item-header text-muted">
@@ -193,6 +192,7 @@
{% endif %}
{% endblock %}
{% block script %}
<script> {% include "templates/pages/order.js" %}</script>
<script>
@@ -204,4 +204,4 @@
currency: '{{ doc.currency }}'
}
</script>
{% endblock %}
{% endblock %}

View File

@@ -1,7 +1,7 @@
{% extends "templates/web.html" %}
{% block header %}
<h1>{{ doc.name }}</h1>
<h1 style="margin-top: 10px;">{{ doc.name }}</h1>
{% endblock %}
{% block script %}
@@ -16,7 +16,7 @@
{% if doc.items %}
<button class="btn btn-primary btn-sm"
type="button">
{{ _("Submit") }}</button>
{{ _("Make Quotation") }}</button>
{% endif %}
{% endblock %}

View File

@@ -5007,7 +5007,7 @@ ACC-PINV-.YYYY.-,ACC-PINV-.JJJJ.-,
Tax Withholding Category,Steuereinbehalt Kategorie,
Edit Posting Date and Time,Buchungsdatum und -uhrzeit bearbeiten,
Is Paid,Ist bezahlt,
Is Return (Debit Note),ist Rücklieferung (Lastschrift),
Is Return (Debit Note),Ist Rechnungskorrektur (Retoure),
Apply Tax Withholding Amount,Steuereinbehaltungsbetrag anwenden,
Accounting Dimensions ,Buchhaltung Dimensionen,
Supplier Invoice Details,Lieferant Rechnungsdetails,
@@ -5133,7 +5133,7 @@ Default Bank / Cash account will be automatically updated in Salary Journal Entr
ACC-SINV-.YYYY.-,ACC-SINV-.JJJJ.-,
Include Payment (POS),(POS) Zahlung einschließen,
Offline POS Name,Offline-Verkaufsstellen-Name,
Is Return (Credit Note),ist Rücklieferung (Gutschrift),
Is Return (Credit Note),Ist Rechnungskorrektur (Retoure),
Update Billed Amount in Sales Order,Aktualisierung des Rechnungsbetrags im Auftrag,
Customer PO Details,Auftragsdetails,
Customer's Purchase Order,Bestellung des Kunden,
@@ -7993,7 +7993,7 @@ Customs Tariff Number,Zolltarifnummer,
Tariff Number,Tarifnummer,
Delivery To,Lieferung an,
MAT-DN-.YYYY.-,MAT-DN-.YYYY.-,
Is Return,Ist Rückgabe,
Is Return,Ist Retoure,
Issue Credit Note,Gutschrift ausgeben,
Return Against Delivery Note,Zurück zum Lieferschein,
Customer's Purchase Order No,Bestellnummer des Kunden,
Can't render this file because it is too large.