mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 19:19:17 +00:00
Merge pull request #54438 from frappe/version-15-hotfix
This commit is contained in:
@@ -82,13 +82,15 @@ class AccountingDimension(Document):
|
|||||||
else:
|
else:
|
||||||
frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company)))
|
frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company)))
|
||||||
|
|
||||||
def after_insert(self):
|
def on_update(self):
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
make_dimension_in_accounting_doctypes(doc=self)
|
make_dimension_in_accounting_doctypes(doc=self)
|
||||||
else:
|
else:
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
make_dimension_in_accounting_doctypes, doc=self, queue="long", enqueue_after_commit=True
|
make_dimension_in_accounting_doctypes, doc=self, queue="long", enqueue_after_commit=True
|
||||||
)
|
)
|
||||||
|
frappe.flags.accounting_dimensions = None
|
||||||
|
frappe.flags.accounting_dimensions_details = None
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
@@ -103,10 +105,6 @@ class AccountingDimension(Document):
|
|||||||
if not self.fieldname:
|
if not self.fieldname:
|
||||||
self.fieldname = scrub(self.label)
|
self.fieldname = scrub(self.label)
|
||||||
|
|
||||||
def on_update(self):
|
|
||||||
frappe.flags.accounting_dimensions = None
|
|
||||||
frappe.flags.accounting_dimensions_details = None
|
|
||||||
|
|
||||||
|
|
||||||
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||||
if not doclist:
|
if not doclist:
|
||||||
|
|||||||
@@ -812,6 +812,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"fetch_from": "item_code.grant_commission",
|
||||||
"fieldname": "grant_commission",
|
"fieldname": "grant_commission",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Grant Commission",
|
"label": "Grant Commission",
|
||||||
@@ -858,7 +859,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-05-07 15:56:54.343317",
|
"modified": "2026-04-20 16:16:12.322024",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice Item",
|
"name": "POS Invoice Item",
|
||||||
|
|||||||
@@ -658,7 +658,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
|||||||
if pricing_rule.is_recursive:
|
if pricing_rule.is_recursive:
|
||||||
transaction_qty = sum(
|
transaction_qty = sum(
|
||||||
[
|
[
|
||||||
row.qty
|
flt(row.qty)
|
||||||
for row in doc.items
|
for row in doc.items
|
||||||
if not row.is_free_item
|
if not row.is_free_item
|
||||||
and row.item_code == args.item_code
|
and row.item_code == args.item_code
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ frappe.ui.form.on("Shipping Rule", {
|
|||||||
},
|
},
|
||||||
calculate_based_on: function (frm) {
|
calculate_based_on: function (frm) {
|
||||||
frm.trigger("toggle_reqd");
|
frm.trigger("toggle_reqd");
|
||||||
|
if (frm.doc.calculate_based_on === "Fixed") {
|
||||||
|
frm.clear_table("conditions");
|
||||||
|
frm.refresh_field("conditions");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
toggle_reqd: function (frm) {
|
toggle_reqd: function (frm) {
|
||||||
frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === "Fixed");
|
frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === "Fixed");
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ class ShippingRule(Document):
|
|||||||
self.validate_overlapping_shipping_rule_conditions()
|
self.validate_overlapping_shipping_rule_conditions()
|
||||||
|
|
||||||
def validate_from_to_values(self):
|
def validate_from_to_values(self):
|
||||||
|
if self.calculate_based_on == "Fixed":
|
||||||
|
if self.conditions:
|
||||||
|
self.set("conditions", [])
|
||||||
|
return
|
||||||
|
|
||||||
zero_to_values = []
|
zero_to_values = []
|
||||||
|
|
||||||
for d in self.get("conditions"):
|
for d in self.get("conditions"):
|
||||||
|
|||||||
@@ -474,6 +474,11 @@ def create_supplier_quotation(doc):
|
|||||||
if isinstance(doc, str):
|
if isinstance(doc, str):
|
||||||
doc = json.loads(doc)
|
doc = json.loads(doc)
|
||||||
|
|
||||||
|
if frappe.session.user not in frappe.get_all(
|
||||||
|
"Portal User", {"parent": doc.get("supplier")}, pluck="user"
|
||||||
|
):
|
||||||
|
frappe.throw(_("Not Permitted"), frappe.PermissionError)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sq_doc = frappe.get_doc(
|
sq_doc = frappe.get_doc(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -263,6 +263,13 @@ def make_request_for_quotation(**args) -> "RequestforQuotation":
|
|||||||
|
|
||||||
for data in supplier_data:
|
for data in supplier_data:
|
||||||
rfq.append("suppliers", data)
|
rfq.append("suppliers", data)
|
||||||
|
frappe.new_doc(
|
||||||
|
"Portal User",
|
||||||
|
user="Administrator",
|
||||||
|
parent=data.get("supplier"),
|
||||||
|
parentfield="portal_users",
|
||||||
|
parenttype="Supplier",
|
||||||
|
).insert()
|
||||||
|
|
||||||
rfq.append(
|
rfq.append(
|
||||||
"items",
|
"items",
|
||||||
|
|||||||
@@ -693,18 +693,17 @@ class calculate_taxes_and_totals:
|
|||||||
if self.doc.meta.get_field("rounded_total"):
|
if self.doc.meta.get_field("rounded_total"):
|
||||||
if self.doc.is_rounded_total_disabled():
|
if self.doc.is_rounded_total_disabled():
|
||||||
self.doc.rounded_total = 0
|
self.doc.rounded_total = 0
|
||||||
self.doc.base_rounded_total = 0
|
|
||||||
self.doc.rounding_adjustment = 0
|
self.doc.rounding_adjustment = 0
|
||||||
return
|
|
||||||
|
|
||||||
self.doc.rounded_total = round_based_on_smallest_currency_fraction(
|
else:
|
||||||
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
|
self.doc.rounded_total = round_based_on_smallest_currency_fraction(
|
||||||
)
|
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
|
||||||
|
)
|
||||||
|
|
||||||
# rounding adjustment should always be the difference vetween grand and rounded total
|
# rounding adjustment should always be the difference between grand and rounded total
|
||||||
self.doc.rounding_adjustment = flt(
|
self.doc.rounding_adjustment = flt(
|
||||||
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
|
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
|
||||||
)
|
)
|
||||||
|
|
||||||
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
|
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
|
||||||
|
|
||||||
|
|||||||
37
erpnext/controllers/tests/test_taxes_and_totals.py
Normal file
37
erpnext/controllers/tests/test_taxes_and_totals.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
|
||||||
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
|
|
||||||
|
class TestTaxesAndTotals(FrappeTestCase):
|
||||||
|
def test_disabling_rounded_total_resets_base_fields(self):
|
||||||
|
"""Disabling rounded total should also clear base rounded values."""
|
||||||
|
so = make_sales_order(do_not_save=True)
|
||||||
|
so.items[0].qty = 1
|
||||||
|
so.items[0].rate = 1000.25
|
||||||
|
so.items[0].price_list_rate = 1000.25
|
||||||
|
so.items[0].discount_percentage = 0
|
||||||
|
so.items[0].discount_amount = 0
|
||||||
|
so.set("taxes", [])
|
||||||
|
|
||||||
|
so.disable_rounded_total = 0
|
||||||
|
calculate_taxes_and_totals(so)
|
||||||
|
|
||||||
|
self.assertEqual(so.grand_total, 1000.25)
|
||||||
|
self.assertEqual(so.rounded_total, 1000.0)
|
||||||
|
self.assertEqual(so.rounding_adjustment, -0.25)
|
||||||
|
self.assertEqual(so.base_grand_total, 1000.25)
|
||||||
|
self.assertEqual(so.base_rounded_total, 1000.0)
|
||||||
|
self.assertEqual(so.base_rounding_adjustment, -0.25)
|
||||||
|
|
||||||
|
# User toggles disable_rounded_total after values are already set.
|
||||||
|
so.disable_rounded_total = 1
|
||||||
|
|
||||||
|
calculate_taxes_and_totals(so)
|
||||||
|
|
||||||
|
self.assertEqual(so.rounded_total, 0)
|
||||||
|
self.assertEqual(so.rounding_adjustment, 0)
|
||||||
|
self.assertEqual(so.base_rounded_total, 0)
|
||||||
|
self.assertEqual(so.base_rounding_adjustment, 0)
|
||||||
@@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import getdate
|
from frappe.utils import DateTimeLikeObject, getdate, today
|
||||||
|
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters, trans):
|
def get_columns(filters, trans):
|
||||||
@@ -45,6 +47,10 @@ def get_columns(filters, trans):
|
|||||||
|
|
||||||
|
|
||||||
def validate_filters(filters):
|
def validate_filters(filters):
|
||||||
|
if not filters.get("fiscal_year"):
|
||||||
|
filters["fiscal_year"] = get_fiscal_year(today())[0]
|
||||||
|
if not filters.get("company"):
|
||||||
|
filters["company"] = frappe.defaults.get_user_default("Company")
|
||||||
for f in ["Fiscal Year", "Based On", "Period", "Company"]:
|
for f in ["Fiscal Year", "Based On", "Period", "Company"]:
|
||||||
if not filters.get(f.lower().replace(" ", "_")):
|
if not filters.get(f.lower().replace(" ", "_")):
|
||||||
frappe.throw(_("{0} is mandatory").format(_(f)))
|
frappe.throw(_("{0} is mandatory").format(_(f)))
|
||||||
|
|||||||
@@ -674,24 +674,27 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
|
|
||||||
set_rounded_total() {
|
set_rounded_total() {
|
||||||
var disable_rounded_total = 0;
|
var disable_rounded_total = 0;
|
||||||
if(frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total", this.frm.doc.name)) {
|
if (frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total", this.frm.doc.name)) {
|
||||||
disable_rounded_total = this.frm.doc.disable_rounded_total;
|
disable_rounded_total = this.frm.doc.disable_rounded_total;
|
||||||
} else if (frappe.sys_defaults.disable_rounded_total) {
|
} else if (frappe.sys_defaults.disable_rounded_total) {
|
||||||
disable_rounded_total = frappe.sys_defaults.disable_rounded_total;
|
disable_rounded_total = frappe.sys_defaults.disable_rounded_total;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cint(disable_rounded_total)) {
|
if (frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
|
||||||
this.frm.doc.rounded_total = 0;
|
if (cint(disable_rounded_total)) {
|
||||||
this.frm.doc.base_rounded_total = 0;
|
this.frm.doc.rounded_total = 0;
|
||||||
this.frm.doc.rounding_adjustment = 0;
|
this.frm.doc.rounding_adjustment = 0;
|
||||||
return;
|
} else {
|
||||||
}
|
this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(
|
||||||
|
this.frm.doc.grand_total,
|
||||||
if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
|
this.frm.doc.currency,
|
||||||
this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.grand_total,
|
precision("rounded_total"),
|
||||||
this.frm.doc.currency, precision("rounded_total"));
|
);
|
||||||
this.frm.doc.rounding_adjustment = flt(this.frm.doc.rounded_total - this.frm.doc.grand_total,
|
this.frm.doc.rounding_adjustment = flt(
|
||||||
precision("rounding_adjustment"));
|
this.frm.doc.rounded_total - this.frm.doc.grand_total,
|
||||||
|
precision("rounding_adjustment"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]);
|
this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ frappe.ui.form.ContactAddressQuickEntryForm = class ContactAddressQuickEntryForm
|
|||||||
{
|
{
|
||||||
fieldtype: "Section Break",
|
fieldtype: "Section Break",
|
||||||
label: __("Primary Contact Details"),
|
label: __("Primary Contact Details"),
|
||||||
collapsible: 1,
|
collapsible: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __("First Name"),
|
label: __("First Name"),
|
||||||
@@ -69,7 +69,7 @@ frappe.ui.form.ContactAddressQuickEntryForm = class ContactAddressQuickEntryForm
|
|||||||
{
|
{
|
||||||
fieldtype: "Section Break",
|
fieldtype: "Section Break",
|
||||||
label: __("Primary Address Details"),
|
label: __("Primary Address Details"),
|
||||||
collapsible: 1,
|
collapsible: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __("Address Line 1"),
|
label: __("Address Line 1"),
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
# import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
from erpnext import get_region
|
||||||
|
|
||||||
|
|
||||||
class SouthAfricaVATSettings(Document):
|
class SouthAfricaVATSettings(Document):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
@@ -22,4 +25,9 @@ class SouthAfricaVATSettings(Document):
|
|||||||
vat_accounts: DF.Table[SouthAfricaVATAccount]
|
vat_accounts: DF.Table[SouthAfricaVATAccount]
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
pass
|
def validate(self):
|
||||||
|
self.validate_company_region()
|
||||||
|
|
||||||
|
def validate_company_region(self):
|
||||||
|
if self.company and get_region(self.company) != "South Africa":
|
||||||
|
frappe.throw(_("Company {0} is not in South Africa.").format(frappe.bold(self.company)))
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ frappe.query_reports["VAT Audit Report"] = {
|
|||||||
options: "Company",
|
options: "Company",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
default: frappe.defaults.get_user_default("Company"),
|
default: frappe.defaults.get_user_default("Company"),
|
||||||
|
get_query: function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
country: "South Africa",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "from_date",
|
fieldname: "from_date",
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ import json
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import Coalesce, NullIf
|
||||||
from frappe.utils import formatdate, get_link_to_form
|
from frappe.utils import formatdate, get_link_to_form
|
||||||
|
|
||||||
|
from erpnext import get_region
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
return VATAuditReport(filters).run()
|
return VATAuditReport(filters).run()
|
||||||
@@ -21,19 +24,10 @@ class VATAuditReport:
|
|||||||
self.doctypes = ["Purchase Invoice", "Sales Invoice"]
|
self.doctypes = ["Purchase Invoice", "Sales Invoice"]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
self.validate_company_region()
|
||||||
self.get_sa_vat_accounts()
|
self.get_sa_vat_accounts()
|
||||||
self.get_columns()
|
self.get_columns()
|
||||||
for doctype in self.doctypes:
|
for doctype in self.doctypes:
|
||||||
self.select_columns = """
|
|
||||||
name as voucher_no,
|
|
||||||
posting_date, remarks"""
|
|
||||||
columns = (
|
|
||||||
", supplier as party, credit_to as account"
|
|
||||||
if doctype == "Purchase Invoice"
|
|
||||||
else ", customer as party, debit_to as account"
|
|
||||||
)
|
|
||||||
self.select_columns += columns
|
|
||||||
|
|
||||||
self.get_invoice_data(doctype)
|
self.get_invoice_data(doctype)
|
||||||
|
|
||||||
if self.invoices:
|
if self.invoices:
|
||||||
@@ -43,6 +37,14 @@ class VATAuditReport:
|
|||||||
|
|
||||||
return self.columns, self.data
|
return self.columns, self.data
|
||||||
|
|
||||||
|
def validate_company_region(self):
|
||||||
|
if self.filters.company and get_region(self.filters.company) != "South Africa":
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"The company {0} is not in South Africa. VAT Audit Report is only available for companies in South Africa."
|
||||||
|
).format(frappe.bold(self.filters.company))
|
||||||
|
)
|
||||||
|
|
||||||
def get_sa_vat_accounts(self):
|
def get_sa_vat_accounts(self):
|
||||||
self.sa_vat_accounts = frappe.get_all(
|
self.sa_vat_accounts = frappe.get_all(
|
||||||
"South Africa VAT Account", filters={"parent": self.filters.company}, pluck="account"
|
"South Africa VAT Account", filters={"parent": self.filters.company}, pluck="account"
|
||||||
@@ -54,47 +56,59 @@ class VATAuditReport:
|
|||||||
frappe.throw(_("Please set VAT Accounts in {0}").format(link_to_settings))
|
frappe.throw(_("Please set VAT Accounts in {0}").format(link_to_settings))
|
||||||
|
|
||||||
def get_invoice_data(self, doctype):
|
def get_invoice_data(self, doctype):
|
||||||
conditions = self.get_conditions()
|
|
||||||
self.invoices = frappe._dict()
|
self.invoices = frappe._dict()
|
||||||
|
invoice_doctype = frappe.qb.DocType(doctype)
|
||||||
invoice_data = frappe.db.sql(
|
party_field = invoice_doctype.supplier if doctype == "Purchase Invoice" else invoice_doctype.customer
|
||||||
f"""
|
account_field = (
|
||||||
SELECT
|
invoice_doctype.credit_to if doctype == "Purchase Invoice" else invoice_doctype.debit_to
|
||||||
{self.select_columns}
|
|
||||||
FROM
|
|
||||||
`tab{doctype}`
|
|
||||||
WHERE
|
|
||||||
docstatus = 1 {conditions}
|
|
||||||
and is_opening = 'No'
|
|
||||||
ORDER BY
|
|
||||||
posting_date DESC
|
|
||||||
""",
|
|
||||||
self.filters,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for d in invoice_data:
|
query = (
|
||||||
self.invoices.setdefault(d.voucher_no, d)
|
frappe.qb.from_(invoice_doctype)
|
||||||
|
.select(
|
||||||
|
invoice_doctype.name.as_("voucher_no"),
|
||||||
|
invoice_doctype.posting_date,
|
||||||
|
invoice_doctype.remarks,
|
||||||
|
party_field.as_("party"),
|
||||||
|
account_field.as_("account"),
|
||||||
|
)
|
||||||
|
.where(invoice_doctype.docstatus == 1)
|
||||||
|
.where(invoice_doctype.is_opening == "No")
|
||||||
|
.orderby(invoice_doctype.posting_date, order=frappe.qb.desc)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.filters.get("company"):
|
||||||
|
query = query.where(invoice_doctype.company == self.filters.company)
|
||||||
|
if self.filters.get("from_date"):
|
||||||
|
query = query.where(invoice_doctype.posting_date >= self.filters.from_date)
|
||||||
|
if self.filters.get("to_date"):
|
||||||
|
query = query.where(invoice_doctype.posting_date <= self.filters.to_date)
|
||||||
|
|
||||||
|
invoice_data = query.run(as_dict=True)
|
||||||
|
|
||||||
|
for row in invoice_data:
|
||||||
|
self.invoices.setdefault(row.voucher_no, row)
|
||||||
|
|
||||||
def get_invoice_items(self, doctype):
|
def get_invoice_items(self, doctype):
|
||||||
self.invoice_items = frappe._dict()
|
self.invoice_items = frappe._dict()
|
||||||
|
item_doctype = frappe.qb.DocType(doctype + " Item")
|
||||||
|
|
||||||
items = frappe.db.sql(
|
items = (
|
||||||
"""
|
frappe.qb.from_(item_doctype)
|
||||||
SELECT
|
.select(
|
||||||
item_code, parent, base_net_amount, is_zero_rated
|
Coalesce(NullIf(item_doctype.item_code, ""), item_doctype.item_name).as_("item"),
|
||||||
FROM
|
item_doctype.parent,
|
||||||
`tab{} Item`
|
item_doctype.base_net_amount,
|
||||||
WHERE
|
item_doctype.is_zero_rated,
|
||||||
parent in ({})
|
)
|
||||||
""".format(doctype, ", ".join(["%s"] * len(self.invoices))),
|
.where(item_doctype.parent.isin(list(self.invoices.keys())))
|
||||||
tuple(self.invoices),
|
.run(as_dict=True)
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
for d in items:
|
|
||||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {"net_amount": 0.0})
|
for row in items:
|
||||||
self.invoice_items[d.parent][d.item_code]["net_amount"] += d.get("base_net_amount", 0)
|
self.invoice_items.setdefault(row.parent, {}).setdefault(row.item, {"net_amount": 0.0})
|
||||||
self.invoice_items[d.parent][d.item_code]["is_zero_rated"] = d.is_zero_rated
|
self.invoice_items[row.parent][row.item]["net_amount"] += row.get("base_net_amount", 0)
|
||||||
|
self.invoice_items[row.parent][row.item]["is_zero_rated"] = row.is_zero_rated
|
||||||
|
|
||||||
def get_items_based_on_tax_rate(self, doctype):
|
def get_items_based_on_tax_rate(self, doctype):
|
||||||
self.items_based_on_tax_rate = frappe._dict()
|
self.items_based_on_tax_rate = frappe._dict()
|
||||||
@@ -103,52 +117,54 @@ class VATAuditReport:
|
|||||||
"Purchase Taxes and Charges" if doctype == "Purchase Invoice" else "Sales Taxes and Charges"
|
"Purchase Taxes and Charges" if doctype == "Purchase Invoice" else "Sales Taxes and Charges"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.tax_details = frappe.db.sql(
|
tax_doctype = frappe.qb.DocType(self.tax_doctype)
|
||||||
"""
|
self.tax_details = (
|
||||||
SELECT
|
frappe.qb.from_(tax_doctype)
|
||||||
parent, account_head, item_wise_tax_detail
|
.select(tax_doctype.parent, tax_doctype.account_head, tax_doctype.item_wise_tax_detail)
|
||||||
FROM
|
.where(tax_doctype.parenttype == doctype)
|
||||||
`tab{}`
|
.where(tax_doctype.docstatus == 1)
|
||||||
WHERE
|
.where(tax_doctype.parent.isin(list(self.invoices.keys())))
|
||||||
parenttype = {} and docstatus = 1
|
.where(tax_doctype.account_head.isin(self.sa_vat_accounts))
|
||||||
and parent in ({})
|
.orderby(tax_doctype.account_head)
|
||||||
ORDER BY
|
.run(as_dict=True)
|
||||||
account_head
|
|
||||||
""".format(self.tax_doctype, "%s", ", ".join(["%s"] * len(self.invoices.keys()))),
|
|
||||||
tuple([doctype, *list(self.invoices.keys())]),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for parent, account, item_wise_tax_detail in self.tax_details:
|
for tax_detail in self.tax_details:
|
||||||
if item_wise_tax_detail:
|
if not tax_detail.item_wise_tax_detail:
|
||||||
try:
|
continue
|
||||||
if account in self.sa_vat_accounts:
|
|
||||||
item_wise_tax_detail = json.loads(item_wise_tax_detail)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
for item_code, taxes in item_wise_tax_detail.items():
|
|
||||||
is_zero_rated = self.invoice_items.get(parent).get(item_code).get("is_zero_rated")
|
|
||||||
# to skip items with non-zero tax rate in multiple rows
|
|
||||||
if taxes[0] == 0 and not is_zero_rated:
|
|
||||||
continue
|
|
||||||
tax_rate = self.get_item_amount_map(parent, item_code, taxes)
|
|
||||||
|
|
||||||
if tax_rate is not None:
|
try:
|
||||||
rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}).setdefault(
|
item_wise_tax_detail = json.loads(tax_detail.item_wise_tax_detail)
|
||||||
tax_rate, []
|
except ValueError:
|
||||||
)
|
continue
|
||||||
if item_code not in rate_based_dict:
|
|
||||||
rate_based_dict.append(item_code)
|
parent_items = self.invoice_items.get(tax_detail.parent, {})
|
||||||
except ValueError:
|
parent_tax_rates = self.items_based_on_tax_rate.setdefault(tax_detail.parent, {})
|
||||||
|
|
||||||
|
for item, taxes in item_wise_tax_detail.items():
|
||||||
|
is_zero_rated = parent_items.get(item, {}).get("is_zero_rated")
|
||||||
|
# to skip items with non-zero tax rate in multiple rows
|
||||||
|
if taxes[0] == 0 and not is_zero_rated:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def get_item_amount_map(self, parent, item_code, taxes):
|
tax_rate = self.get_item_amount_map(tax_detail.parent, item, taxes)
|
||||||
net_amount = self.invoice_items.get(parent).get(item_code).get("net_amount")
|
if tax_rate is not None:
|
||||||
|
rate_based_dict = parent_tax_rates.setdefault(tax_rate, [])
|
||||||
|
if item not in rate_based_dict:
|
||||||
|
rate_based_dict.append(item)
|
||||||
|
|
||||||
|
def get_item_amount_map(self, parent, item, taxes):
|
||||||
|
item_details = self.invoice_items.get(parent, {}).get(item)
|
||||||
|
if not item_details:
|
||||||
|
return None
|
||||||
|
|
||||||
|
net_amount = item_details.get("net_amount", 0)
|
||||||
tax_rate = taxes[0]
|
tax_rate = taxes[0]
|
||||||
tax_amount = taxes[1]
|
tax_amount = taxes[1]
|
||||||
gross_amount = net_amount + tax_amount
|
gross_amount = net_amount + tax_amount
|
||||||
|
|
||||||
self.item_tax_rate.setdefault(parent, {}).setdefault(
|
self.item_tax_rate.setdefault(parent, {}).setdefault(
|
||||||
item_code,
|
item,
|
||||||
{
|
{
|
||||||
"tax_rate": tax_rate,
|
"tax_rate": tax_rate,
|
||||||
"gross_amount": 0.0,
|
"gross_amount": 0.0,
|
||||||
@@ -157,24 +173,12 @@ class VATAuditReport:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
self.item_tax_rate[parent][item_code]["net_amount"] += net_amount
|
self.item_tax_rate[parent][item]["net_amount"] += net_amount
|
||||||
self.item_tax_rate[parent][item_code]["tax_amount"] += tax_amount
|
self.item_tax_rate[parent][item]["tax_amount"] += tax_amount
|
||||||
self.item_tax_rate[parent][item_code]["gross_amount"] += gross_amount
|
self.item_tax_rate[parent][item]["gross_amount"] += gross_amount
|
||||||
|
|
||||||
return tax_rate
|
return tax_rate
|
||||||
|
|
||||||
def get_conditions(self):
|
|
||||||
conditions = ""
|
|
||||||
for opts in (
|
|
||||||
("company", " and company=%(company)s"),
|
|
||||||
("from_date", " and posting_date>=%(from_date)s"),
|
|
||||||
("to_date", " and posting_date<=%(to_date)s"),
|
|
||||||
):
|
|
||||||
if self.filters.get(opts[0]):
|
|
||||||
conditions += opts[1]
|
|
||||||
|
|
||||||
return conditions
|
|
||||||
|
|
||||||
def get_data(self, doctype):
|
def get_data(self, doctype):
|
||||||
consolidated_data = self.get_consolidated_data(doctype)
|
consolidated_data = self.get_consolidated_data(doctype)
|
||||||
section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales")
|
section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales")
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ class Quotation(SellingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for row in self._items:
|
for row in self._items:
|
||||||
if row.name not in ordered_items or row.qty > ordered_items[row.name]:
|
if row.name not in ordered_items or row.stock_qty > ordered_items[row.name]:
|
||||||
return "Partially Ordered"
|
return "Partially Ordered"
|
||||||
|
|
||||||
return "Ordered"
|
return "Ordered"
|
||||||
@@ -409,9 +409,9 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
|||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
|
||||||
def update_item(obj, target, source_parent):
|
def update_item(obj, target, source_parent):
|
||||||
balance_qty = obj.qty if is_unit_price_row(obj) else obj.qty - ordered_items.get(obj.name, 0.0)
|
balance_stock_qty = obj.stock_qty - ordered_items.get(obj.name, 0.0)
|
||||||
target.qty = balance_qty if balance_qty > 0 else 0
|
target.stock_qty = balance_stock_qty if balance_stock_qty > 0 else 0
|
||||||
target.stock_qty = flt(target.qty) * flt(obj.conversion_factor)
|
target.qty = flt(target.stock_qty) / flt(obj.conversion_factor)
|
||||||
|
|
||||||
if obj.against_blanket_order:
|
if obj.against_blanket_order:
|
||||||
target.against_blanket_order = obj.against_blanket_order
|
target.against_blanket_order = obj.against_blanket_order
|
||||||
@@ -425,7 +425,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
|||||||
2. If selections: Is Alternative Item/Has Alternative Item: Map if selected and adequate qty
|
2. If selections: Is Alternative Item/Has Alternative Item: Map if selected and adequate qty
|
||||||
3. If no selections: Simple row: Map if adequate qty
|
3. If no selections: Simple row: Map if adequate qty
|
||||||
"""
|
"""
|
||||||
if not ((item.qty > ordered_items.get(item.name, 0.0)) or is_unit_price_row(item)):
|
if not ((item.stock_qty > ordered_items.get(item.name, 0.0)) or is_unit_price_row(item)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not selected_rows:
|
if not selected_rows:
|
||||||
|
|||||||
@@ -791,6 +791,20 @@ class Item(Document):
|
|||||||
{"company": defaults.get("company"), "default_warehouse": defaults.default_warehouse},
|
{"company": defaults.get("company"), "default_warehouse": defaults.default_warehouse},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
item_group = frappe.get_cached_doc("Item Group", self.item_group)
|
||||||
|
if not self.taxes and item_group.taxes:
|
||||||
|
for tax in item_group.taxes:
|
||||||
|
self.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"item_tax_template": tax.item_tax_template,
|
||||||
|
"tax_category": tax.tax_category,
|
||||||
|
"valid_from": tax.valid_from,
|
||||||
|
"minimum_net_rate": tax.minimum_net_rate,
|
||||||
|
"maximum_net_rate": tax.maximum_net_rate,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def update_variants(self):
|
def update_variants(self):
|
||||||
if self.flags.dont_update_variants or frappe.db.get_single_value(
|
if self.flags.dont_update_variants or frappe.db.get_single_value(
|
||||||
"Item Variant Settings", "do_not_update_variants"
|
"Item Variant Settings", "do_not_update_variants"
|
||||||
|
|||||||
@@ -90,45 +90,62 @@ def get_data(filters) -> list[dict]:
|
|||||||
batch_negative_data = []
|
batch_negative_data = []
|
||||||
|
|
||||||
flt_precision = frappe.db.get_default("float_precision") or 2
|
flt_precision = frappe.db.get_default("float_precision") or 2
|
||||||
|
distinct_batches = set()
|
||||||
for company in companies:
|
for company in companies:
|
||||||
for batch in batches:
|
warehouses = get_warehouses(filters, company)
|
||||||
_c, data = stock_ledger_execute(
|
for warehouse in warehouses:
|
||||||
frappe._dict(
|
for batch in batches:
|
||||||
{
|
_c, data = stock_ledger_execute(
|
||||||
"company": company,
|
frappe._dict(
|
||||||
"batch_no": batch,
|
|
||||||
"from_date": add_to_date(today(), years=-12),
|
|
||||||
"to_date": today(),
|
|
||||||
"segregate_serial_batch_bundle": 1,
|
|
||||||
"warehouse": filters.get("warehouse"),
|
|
||||||
"valuation_field_type": "Currency",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
previous_qty = 0
|
|
||||||
for row in data:
|
|
||||||
if flt(row.get("qty_after_transaction"), flt_precision) < 0:
|
|
||||||
batch_negative_data.append(
|
|
||||||
{
|
{
|
||||||
"posting_date": row.get("date"),
|
"company": company,
|
||||||
"batch_no": row.get("batch_no"),
|
"batch_no": batch,
|
||||||
"item_code": row.get("item_code"),
|
"from_date": add_to_date(today(), years=-12),
|
||||||
"item_name": row.get("item_name"),
|
"to_date": today(),
|
||||||
"warehouse": row.get("warehouse"),
|
"segregate_serial_batch_bundle": 1,
|
||||||
"actual_qty": row.get("actual_qty"),
|
"warehouse": warehouse,
|
||||||
"qty_after_transaction": row.get("qty_after_transaction"),
|
"valuation_field_type": "Currency",
|
||||||
"previous_qty": previous_qty,
|
|
||||||
"voucher_type": row.get("voucher_type"),
|
|
||||||
"voucher_no": row.get("voucher_no"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
previous_qty = row.get("qty_after_transaction")
|
previous_qty = 0
|
||||||
|
for row in data:
|
||||||
|
key = (row.get("warehouse"), batch)
|
||||||
|
if key in distinct_batches:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if flt(row.get("qty_after_transaction"), flt_precision) < 0:
|
||||||
|
batch_negative_data.append(
|
||||||
|
{
|
||||||
|
"posting_date": row.get("date"),
|
||||||
|
"batch_no": row.get("batch_no"),
|
||||||
|
"item_code": row.get("item_code"),
|
||||||
|
"item_name": row.get("item_name"),
|
||||||
|
"warehouse": row.get("warehouse"),
|
||||||
|
"actual_qty": row.get("actual_qty"),
|
||||||
|
"qty_after_transaction": row.get("qty_after_transaction"),
|
||||||
|
"previous_qty": previous_qty,
|
||||||
|
"voucher_type": row.get("voucher_type"),
|
||||||
|
"voucher_no": row.get("voucher_no"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
distinct_batches.add(key)
|
||||||
|
|
||||||
|
previous_qty = row.get("qty_after_transaction")
|
||||||
|
|
||||||
return batch_negative_data
|
return batch_negative_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_warehouses(filters, company):
|
||||||
|
warehouse_filters = {"company": company, "disabled": 0}
|
||||||
|
if filters.get("warehouse"):
|
||||||
|
warehouse_filters["name"] = filters["warehouse"]
|
||||||
|
|
||||||
|
return frappe.get_all("Warehouse", pluck="name", filters=warehouse_filters)
|
||||||
|
|
||||||
|
|
||||||
def get_batches(filters):
|
def get_batches(filters):
|
||||||
batch_filters = {}
|
batch_filters = {}
|
||||||
if filters.get("item_code"):
|
if filters.get("item_code"):
|
||||||
|
|||||||
Reference in New Issue
Block a user