mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 19:19:17 +00:00
Merge pull request #31725 from frappe/version-13-hotfix
chore: weekly version-13 release
This commit is contained in:
@@ -305,7 +305,7 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
def validate_reference_documents(self):
|
def validate_reference_documents(self):
|
||||||
if self.party_type == "Student":
|
if self.party_type == "Student":
|
||||||
valid_reference_doctypes = "Fees"
|
valid_reference_doctypes = ("Fees", "Journal Entry")
|
||||||
elif self.party_type == "Customer":
|
elif self.party_type == "Customer":
|
||||||
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
|
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
|
||||||
elif self.party_type == "Supplier":
|
elif self.party_type == "Supplier":
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from frappe import _
|
|||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import map_child_doc, map_doc
|
from frappe.model.mapper import map_child_doc, map_doc
|
||||||
from frappe.utils import flt, getdate, nowdate
|
from frappe.utils import cint, flt, getdate, nowdate
|
||||||
from frappe.utils.background_jobs import enqueue
|
from frappe.utils.background_jobs import enqueue
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
@@ -219,6 +219,9 @@ class POSInvoiceMergeLog(Document):
|
|||||||
invoice.taxes_and_charges = None
|
invoice.taxes_and_charges = None
|
||||||
invoice.ignore_pricing_rule = 1
|
invoice.ignore_pricing_rule = 1
|
||||||
invoice.customer = self.customer
|
invoice.customer = self.customer
|
||||||
|
invoice.disable_rounded_total = cint(
|
||||||
|
frappe.db.get_value("POS Profile", invoice.pos_profile, "disable_rounded_total")
|
||||||
|
)
|
||||||
|
|
||||||
if self.merge_invoices_based_on == "Customer Group":
|
if self.merge_invoices_based_on == "Customer Group":
|
||||||
invoice.flags.ignore_pos_profile = True
|
invoice.flags.ignore_pos_profile = True
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"write_off_account",
|
"write_off_account",
|
||||||
"write_off_cost_center",
|
"write_off_cost_center",
|
||||||
"account_for_change_amount",
|
"account_for_change_amount",
|
||||||
|
"disable_rounded_total",
|
||||||
"column_break_23",
|
"column_break_23",
|
||||||
"income_account",
|
"income_account",
|
||||||
"expense_account",
|
"expense_account",
|
||||||
@@ -358,6 +359,13 @@
|
|||||||
"fieldname": "validate_stock_on_save",
|
"fieldname": "validate_stock_on_save",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Validate Stock on Save"
|
"label": "Validate Stock on Save"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "If enabled, the consolidated invoices will have rounded total disabled",
|
||||||
|
"fieldname": "disable_rounded_total",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disable Rounded Total"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -385,7 +393,7 @@
|
|||||||
"link_fieldname": "pos_profile"
|
"link_fieldname": "pos_profile"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-03-21 13:29:28.480533",
|
"modified": "2022-07-21 11:16:46.911173",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Profile",
|
"name": "POS Profile",
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if tds_category and not for_validate:
|
if tds_category and not for_validate:
|
||||||
self.apply_tds = 1
|
self.apply_tds = 1
|
||||||
self.tax_withholding_category = tds_category
|
self.tax_withholding_category = tds_category
|
||||||
|
self.set_onload("supplier_tds", tds_category)
|
||||||
|
|
||||||
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
|
|||||||
@@ -414,7 +414,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval: doc.is_return && doc.return_against",
|
"depends_on": "eval: doc.is_return",
|
||||||
"fieldname": "update_billed_amount_in_sales_order",
|
"fieldname": "update_billed_amount_in_sales_order",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@@ -2046,7 +2046,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-06-16 16:22:44.870575",
|
"modified": "2022-07-11 17:43:56.435382",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -2712,6 +2712,19 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20)
|
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20)
|
||||||
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10)
|
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10)
|
||||||
|
|
||||||
|
si = get_sales_invoice_for_e_invoice()
|
||||||
|
si.apply_discount_on = ""
|
||||||
|
si.items[1].price_list_rate = 15
|
||||||
|
si.items[1].discount_amount = -5
|
||||||
|
si.items[1].rate = 20
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
|
||||||
|
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20)
|
||||||
|
|
||||||
def test_einvoice_without_discounts(self):
|
def test_einvoice_without_discounts(self):
|
||||||
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
||||||
|
|
||||||
@@ -2804,6 +2817,19 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 18)
|
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 18)
|
||||||
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 5)
|
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 5)
|
||||||
|
|
||||||
|
si = get_sales_invoice_for_e_invoice()
|
||||||
|
si.apply_discount_on = ""
|
||||||
|
si.items[1].price_list_rate = 15
|
||||||
|
si.items[1].discount_amount = -5
|
||||||
|
si.items[1].rate = 20
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
|
||||||
|
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20)
|
||||||
|
|
||||||
def test_item_tax_net_range(self):
|
def test_item_tax_net_range(self):
|
||||||
item = create_item("T Shirt")
|
item = create_item("T Shirt")
|
||||||
|
|
||||||
|
|||||||
@@ -614,13 +614,13 @@ class SellingController(StockController):
|
|||||||
stock_items = [d.item_code, d.description, d.warehouse, ""]
|
stock_items = [d.item_code, d.description, d.warehouse, ""]
|
||||||
non_stock_items = [d.item_code, d.description]
|
non_stock_items = [d.item_code, d.description]
|
||||||
|
|
||||||
|
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
|
||||||
|
duplicate_items_msg += "<br><br>"
|
||||||
|
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
|
||||||
|
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
|
||||||
|
get_link_to_form("Selling Settings", "Selling Settings"),
|
||||||
|
)
|
||||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
||||||
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
|
|
||||||
duplicate_items_msg += "<br><br>"
|
|
||||||
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
|
|
||||||
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
|
|
||||||
get_link_to_form("Selling Settings", "Selling Settings"),
|
|
||||||
)
|
|
||||||
if stock_items in check_list:
|
if stock_items in check_list:
|
||||||
frappe.throw(duplicate_items_msg)
|
frappe.throw(duplicate_items_msg)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "website_item.image",
|
"fetch_from": "website_item.website_image",
|
||||||
"fieldname": "website_item_image",
|
"fieldname": "website_item_image",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"label": "Website Item Image",
|
"label": "Website Item Image",
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-07-13 21:02:19.031652",
|
"modified": "2022-06-28 16:44:24.718728",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "E-commerce",
|
"module": "E-commerce",
|
||||||
"name": "Recommended Items",
|
"name": "Recommended Items",
|
||||||
@@ -83,5 +83,6 @@
|
|||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -30,10 +30,6 @@ frappe.ui.form.on('Website Item', {
|
|||||||
}, __("View"));
|
}, __("View"));
|
||||||
},
|
},
|
||||||
|
|
||||||
image: () => {
|
|
||||||
refresh_field("image_view");
|
|
||||||
},
|
|
||||||
|
|
||||||
copy_from_item_group: (frm) => {
|
copy_from_item_group: (frm) => {
|
||||||
return frm.call({
|
return frm.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"column_break_11",
|
"column_break_11",
|
||||||
"description",
|
"description",
|
||||||
"brand",
|
"brand",
|
||||||
"image",
|
|
||||||
"display_section",
|
"display_section",
|
||||||
"website_image",
|
"website_image",
|
||||||
"website_image_alt",
|
"website_image_alt",
|
||||||
@@ -113,8 +112,11 @@
|
|||||||
{
|
{
|
||||||
"description": "Item Image (if not slideshow)",
|
"description": "Item Image (if not slideshow)",
|
||||||
"fieldname": "website_image",
|
"fieldname": "website_image",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach Image",
|
||||||
"label": "Website Image"
|
"hidden": 1,
|
||||||
|
"in_preview": 1,
|
||||||
|
"label": "Website Image",
|
||||||
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Image Alternative Text",
|
"description": "Image Alternative Text",
|
||||||
@@ -188,14 +190,6 @@
|
|||||||
"options": "Item Group",
|
"options": "Item Group",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "image",
|
|
||||||
"fieldtype": "Attach Image",
|
|
||||||
"hidden": 1,
|
|
||||||
"in_preview": 1,
|
|
||||||
"label": "Image",
|
|
||||||
"print_hide": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "published",
|
"fieldname": "published",
|
||||||
@@ -348,13 +342,14 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 1,
|
"has_web_view": 1,
|
||||||
"image_field": "image",
|
"image_field": "website_image",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-02 13:08:41.942726",
|
"modified": "2022-06-28 17:10:30.613251",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "E-commerce",
|
"module": "E-commerce",
|
||||||
"name": "Website Item",
|
"name": "Website Item",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -410,6 +405,7 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"title_field": "web_item_name",
|
"title_field": "web_item_name",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from typing import TYPE_CHECKING, List, Union
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from erpnext.stock.doctype.item.item import Item
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@@ -116,11 +120,6 @@ class WebsiteItem(WebsiteGenerator):
|
|||||||
if frappe.flags.in_import:
|
if frappe.flags.in_import:
|
||||||
return
|
return
|
||||||
|
|
||||||
auto_set_website_image = False
|
|
||||||
if not self.website_image and self.image:
|
|
||||||
auto_set_website_image = True
|
|
||||||
self.website_image = self.image
|
|
||||||
|
|
||||||
if not self.website_image:
|
if not self.website_image:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -137,18 +136,16 @@ class WebsiteItem(WebsiteGenerator):
|
|||||||
file_doc = file_doc[0]
|
file_doc = file_doc[0]
|
||||||
|
|
||||||
if not file_doc:
|
if not file_doc:
|
||||||
if not auto_set_website_image:
|
frappe.msgprint(
|
||||||
frappe.msgprint(
|
_("Website Image {0} attached to Item {1} cannot be found").format(
|
||||||
_("Website Image {0} attached to Item {1} cannot be found").format(
|
self.website_image, self.name
|
||||||
self.website_image, self.name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.website_image = None
|
self.website_image = None
|
||||||
|
|
||||||
elif file_doc.is_private:
|
elif file_doc.is_private:
|
||||||
if not auto_set_website_image:
|
frappe.msgprint(_("Website Image should be a public file or website URL"))
|
||||||
frappe.msgprint(_("Website Image should be a public file or website URL"))
|
|
||||||
|
|
||||||
self.website_image = None
|
self.website_image = None
|
||||||
|
|
||||||
@@ -159,9 +156,8 @@ class WebsiteItem(WebsiteGenerator):
|
|||||||
|
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
|
||||||
if not self.is_new() and self.website_image != frappe.db.get_value(
|
db_website_image = frappe.db.get_value(self.doctype, self.name, "website_image")
|
||||||
self.doctype, self.name, "website_image"
|
if not self.is_new() and self.website_image != db_website_image:
|
||||||
):
|
|
||||||
self.thumbnail = None
|
self.thumbnail = None
|
||||||
|
|
||||||
if self.website_image and not self.thumbnail:
|
if self.website_image and not self.thumbnail:
|
||||||
@@ -437,7 +433,9 @@ def check_if_user_is_customer(user=None):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_website_item(doc, save=True):
|
def make_website_item(doc: "Item", save: bool = True) -> Union["WebsiteItem", List[str]]:
|
||||||
|
"Make Website Item from Item. Used via Form UI or patch."
|
||||||
|
|
||||||
if not doc:
|
if not doc:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -457,7 +455,6 @@ def make_website_item(doc, save=True):
|
|||||||
"item_group",
|
"item_group",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"brand",
|
"brand",
|
||||||
"image",
|
|
||||||
"has_variants",
|
"has_variants",
|
||||||
"variant_of",
|
"variant_of",
|
||||||
"description",
|
"description",
|
||||||
@@ -465,6 +462,10 @@ def make_website_item(doc, save=True):
|
|||||||
for field in fields_to_map:
|
for field in fields_to_map:
|
||||||
website_item.update({field: doc.get(field)})
|
website_item.update({field: doc.get(field)})
|
||||||
|
|
||||||
|
# Needed for publishing/mapping via Form UI only
|
||||||
|
if not frappe.flags.in_migrate and (doc.get("image") and not website_item.website_image):
|
||||||
|
website_item.website_image = doc.get("image")
|
||||||
|
|
||||||
if not save:
|
if not save:
|
||||||
return website_item
|
return website_item
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
frappe.listview_settings['Website Item'] = {
|
frappe.listview_settings['Website Item'] = {
|
||||||
add_fields: ["item_name", "web_item_name", "published", "image", "has_variants", "variant_of"],
|
add_fields: ["item_name", "web_item_name", "published", "website_image", "has_variants", "variant_of"],
|
||||||
filters: [["published", "=", "1"]],
|
filters: [["published", "=", "1"]],
|
||||||
|
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
|
|||||||
@@ -20,7 +20,15 @@ def add_to_wishlist(item_code):
|
|||||||
web_item_data = frappe.db.get_value(
|
web_item_data = frappe.db.get_value(
|
||||||
"Website Item",
|
"Website Item",
|
||||||
{"item_code": item_code},
|
{"item_code": item_code},
|
||||||
["image", "website_warehouse", "name", "web_item_name", "item_name", "item_group", "route"],
|
[
|
||||||
|
"website_image",
|
||||||
|
"website_warehouse",
|
||||||
|
"name",
|
||||||
|
"web_item_name",
|
||||||
|
"item_name",
|
||||||
|
"item_group",
|
||||||
|
"route",
|
||||||
|
],
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +38,7 @@ def add_to_wishlist(item_code):
|
|||||||
"item_group": web_item_data.get("item_group"),
|
"item_group": web_item_data.get("item_group"),
|
||||||
"website_item": web_item_data.get("name"),
|
"website_item": web_item_data.get("name"),
|
||||||
"web_item_name": web_item_data.get("web_item_name"),
|
"web_item_name": web_item_data.get("web_item_name"),
|
||||||
"image": web_item_data.get("image"),
|
"image": web_item_data.get("website_image"),
|
||||||
"warehouse": web_item_data.get("website_warehouse"),
|
"warehouse": web_item_data.get("website_warehouse"),
|
||||||
"route": web_item_data.get("route"),
|
"route": web_item_data.get("route"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class ProductQuery:
|
|||||||
"variant_of",
|
"variant_of",
|
||||||
"has_variants",
|
"has_variants",
|
||||||
"item_group",
|
"item_group",
|
||||||
"image",
|
|
||||||
"web_long_description",
|
"web_long_description",
|
||||||
"short_description",
|
"short_description",
|
||||||
"route",
|
"route",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ erpnext.ProductGrid = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_image_html(item, title) {
|
get_image_html(item, title) {
|
||||||
let image = item.website_image || item.image;
|
let image = item.website_image;
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
return `
|
return `
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ erpnext.ProductList = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_image_html(item, title, settings) {
|
get_image_html(item, title, settings) {
|
||||||
let image = item.website_image || item.image;
|
let image = item.website_image;
|
||||||
let wishlist_enabled = !item.has_variants && settings.enable_wishlist;
|
let wishlist_enabled = !item.has_variants && settings.enable_wishlist;
|
||||||
let image_html = ``;
|
let image_html = ``;
|
||||||
|
|
||||||
|
|||||||
@@ -199,16 +199,32 @@ def get_fee_components(fee_structure):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_fee_schedule(program, student_category=None):
|
def get_fee_schedule(program, student_category=None, academic_year=None):
|
||||||
"""Returns Fee Schedule.
|
"""Returns Fee Schedule.
|
||||||
|
|
||||||
:param program: Program.
|
:param program: Program.
|
||||||
:param student_category: Student Category
|
:param student_category: Student Category.
|
||||||
|
:param academic_year: Academic Year.
|
||||||
"""
|
"""
|
||||||
fs = frappe.get_all(
|
filters = {}
|
||||||
"Program Fee",
|
if program:
|
||||||
fields=["academic_term", "fee_structure", "due_date", "amount"],
|
filters = {"program": program}
|
||||||
filters={"parent": program, "student_category": student_category},
|
|
||||||
|
if student_category:
|
||||||
|
filters["student_category"] = student_category
|
||||||
|
|
||||||
|
if academic_year:
|
||||||
|
filters["academic_year"] = academic_year
|
||||||
|
|
||||||
|
fs = frappe.db.get_list(
|
||||||
|
"Fee Schedule",
|
||||||
|
filters=filters,
|
||||||
|
fields=[
|
||||||
|
"academic_term",
|
||||||
|
"fee_structure",
|
||||||
|
"student_category",
|
||||||
|
"due_date",
|
||||||
|
"total_amount as amount",
|
||||||
|
],
|
||||||
order_by="idx",
|
order_by="idx",
|
||||||
)
|
)
|
||||||
return fs
|
return fs
|
||||||
|
|||||||
@@ -60,12 +60,15 @@ frappe.ui.form.on('Program Enrollment', {
|
|||||||
method: 'erpnext.education.api.get_fee_schedule',
|
method: 'erpnext.education.api.get_fee_schedule',
|
||||||
args: {
|
args: {
|
||||||
'program': frm.doc.program,
|
'program': frm.doc.program,
|
||||||
'student_category': frm.doc.student_category
|
'student_category': frm.doc.student_category,
|
||||||
|
'academic_year': frm.doc.academic_year
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message) {
|
if (r.message) {
|
||||||
|
cur_frm.clear_table("fees");
|
||||||
|
frm.refresh_fields('fees');
|
||||||
frm.set_value('fees' ,r.message);
|
frm.set_value('fees' ,r.message);
|
||||||
frm.events.get_courses(frm);
|
frm.refresh_fields('fees');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -76,6 +79,10 @@ frappe.ui.form.on('Program Enrollment', {
|
|||||||
frappe.ui.form.trigger('Program Enrollment', 'program');
|
frappe.ui.form.trigger('Program Enrollment', 'program');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
academic_year: function() {
|
||||||
|
frappe.ui.form.trigger('Program Enrollment', 'program');
|
||||||
|
},
|
||||||
|
|
||||||
get_courses: function(frm) {
|
get_courses: function(frm) {
|
||||||
frm.set_value('courses',[]);
|
frm.set_value('courses',[]);
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ class ProgramEnrollment(Document):
|
|||||||
"academic_term": d.academic_term,
|
"academic_term": d.academic_term,
|
||||||
"fee_structure": d.fee_structure,
|
"fee_structure": d.fee_structure,
|
||||||
"program": self.program,
|
"program": self.program,
|
||||||
|
"student_batch": self.student_batch_name,
|
||||||
|
"student_category": self.student_category,
|
||||||
"due_date": d.due_date,
|
"due_date": d.due_date,
|
||||||
"student_name": self.student_name,
|
"student_name": self.student_name,
|
||||||
"program_enrollment": self.name,
|
"program_enrollment": self.name,
|
||||||
|
|||||||
@@ -305,12 +305,11 @@ class ExpenseClaim(AccountsController):
|
|||||||
|
|
||||||
if self.total_advance_amount:
|
if self.total_advance_amount:
|
||||||
precision = self.precision("total_advance_amount")
|
precision = self.precision("total_advance_amount")
|
||||||
if flt(self.total_advance_amount, precision) > flt(self.total_claimed_amount, precision):
|
amount_with_taxes = flt(self.total_sanctioned_amount, precision) + flt(
|
||||||
frappe.throw(_("Total advance amount cannot be greater than total claimed amount"))
|
self.total_taxes_and_charges, precision
|
||||||
|
)
|
||||||
|
|
||||||
if self.total_sanctioned_amount and flt(self.total_advance_amount, precision) > flt(
|
if flt(self.total_advance_amount, precision) > amount_with_taxes:
|
||||||
self.total_sanctioned_amount, precision
|
|
||||||
):
|
|
||||||
frappe.throw(_("Total advance amount cannot be greater than total sanctioned amount"))
|
frappe.throw(_("Total advance amount cannot be greater than total sanctioned amount"))
|
||||||
|
|
||||||
def validate_sanctioned_amount(self):
|
def validate_sanctioned_amount(self):
|
||||||
|
|||||||
@@ -114,6 +114,40 @@ class TestExpenseClaim(FrappeTestCase):
|
|||||||
self.assertEqual(claim.grand_total, 0)
|
self.assertEqual(claim.grand_total, 0)
|
||||||
self.assertEqual(claim.status, "Paid")
|
self.assertEqual(claim.status, "Paid")
|
||||||
|
|
||||||
|
def test_advance_amount_allocation_against_claim_with_taxes(self):
|
||||||
|
from erpnext.hr.doctype.employee_advance.test_employee_advance import (
|
||||||
|
get_advances_for_claim,
|
||||||
|
make_employee_advance,
|
||||||
|
make_payment_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.delete("Employee Advance")
|
||||||
|
|
||||||
|
payable_account = get_payable_account("_Test Company")
|
||||||
|
taxes = generate_taxes("_Test Company")
|
||||||
|
claim = make_expense_claim(
|
||||||
|
payable_account,
|
||||||
|
700,
|
||||||
|
700,
|
||||||
|
"_Test Company",
|
||||||
|
"Travel Expenses - _TC",
|
||||||
|
do_not_submit=True,
|
||||||
|
taxes=taxes,
|
||||||
|
)
|
||||||
|
claim.save()
|
||||||
|
|
||||||
|
advance = make_employee_advance(claim.employee)
|
||||||
|
pe = make_payment_entry(advance)
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
# claim for already paid out advances
|
||||||
|
claim = get_advances_for_claim(claim, advance.name, 763)
|
||||||
|
claim.save()
|
||||||
|
claim.submit()
|
||||||
|
|
||||||
|
self.assertEqual(claim.grand_total, 0)
|
||||||
|
self.assertEqual(claim.status, "Paid")
|
||||||
|
|
||||||
def test_expense_claim_partially_paid_via_advance(self):
|
def test_expense_claim_partially_paid_via_advance(self):
|
||||||
from erpnext.hr.doctype.employee_advance.test_employee_advance import (
|
from erpnext.hr.doctype.employee_advance.test_employee_advance import (
|
||||||
get_advances_for_claim,
|
get_advances_for_claim,
|
||||||
@@ -300,12 +334,13 @@ def get_payable_account(company):
|
|||||||
return frappe.get_cached_value("Company", company, "default_payable_account")
|
return frappe.get_cached_value("Company", company, "default_payable_account")
|
||||||
|
|
||||||
|
|
||||||
def generate_taxes():
|
def generate_taxes(company=None):
|
||||||
|
company = company or company_name
|
||||||
parent_account = frappe.db.get_value(
|
parent_account = frappe.db.get_value(
|
||||||
"Account", {"company": company_name, "is_group": 1, "account_type": "Tax"}, "name"
|
"Account", filters={"account_name": "Duties and Taxes", "company": company}
|
||||||
)
|
)
|
||||||
account = create_account(
|
account = create_account(
|
||||||
company=company_name,
|
company=company,
|
||||||
account_name="Output Tax CGST",
|
account_name="Output Tax CGST",
|
||||||
account_type="Tax",
|
account_type="Tax",
|
||||||
parent_account=parent_account,
|
parent_account=parent_account,
|
||||||
|
|||||||
@@ -21,13 +21,18 @@ frappe.ui.form.on('Member', {
|
|||||||
|
|
||||||
// custom buttons
|
// custom buttons
|
||||||
frm.add_custom_button(__('Accounting Ledger'), function() {
|
frm.add_custom_button(__('Accounting Ledger'), function() {
|
||||||
frappe.set_route('query-report', 'General Ledger',
|
if (frm.doc.customer) {
|
||||||
{party_type:'Member', party:frm.doc.name});
|
frappe.set_route('query-report', 'General Ledger', {party_type: 'Customer', party: frm.doc.customer});
|
||||||
|
} else {
|
||||||
|
frappe.set_route('query-report', 'General Ledger', {party_type: 'Member', party: frm.doc.name});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.add_custom_button(__('Accounts Receivable'), function() {
|
if (frm.doc.customer) {
|
||||||
frappe.set_route('query-report', 'Accounts Receivable', {member:frm.doc.name});
|
frm.add_custom_button(__('Accounts Receivable'), function() {
|
||||||
});
|
frappe.set_route('query-report', 'Accounts Receivable', {customer: frm.doc.customer});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!frm.doc.customer) {
|
if (!frm.doc.customer) {
|
||||||
frm.add_custom_button(__('Create Customer'), () => {
|
frm.add_custom_button(__('Create Customer'), () => {
|
||||||
|
|||||||
@@ -371,3 +371,4 @@ erpnext.patches.v13_0.add_cost_center_in_loans
|
|||||||
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
|
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
|
||||||
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
|
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
|
||||||
erpnext.patches.v13_0.reset_corrupt_defaults
|
erpnext.patches.v13_0.reset_corrupt_defaults
|
||||||
|
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ def execute():
|
|||||||
"item_group",
|
"item_group",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"brand",
|
"brand",
|
||||||
"image",
|
|
||||||
"has_variants",
|
"has_variants",
|
||||||
"variant_of",
|
"variant_of",
|
||||||
"description",
|
"description",
|
||||||
@@ -30,6 +29,7 @@ def execute():
|
|||||||
"website_warehouse",
|
"website_warehouse",
|
||||||
"web_long_description",
|
"web_long_description",
|
||||||
"website_content",
|
"website_content",
|
||||||
|
"website_image",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
16
erpnext/patches/v13_0/show_hr_payroll_deprecation_warning.py
Normal file
16
erpnext/patches/v13_0/show_hr_payroll_deprecation_warning.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import click
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if "hrms" in frappe.get_installed_apps():
|
||||||
|
return
|
||||||
|
|
||||||
|
click.secho(
|
||||||
|
"HR and Payroll modules have been moved to a separate app"
|
||||||
|
" and will be removed from ERPNext in Version 14."
|
||||||
|
" Please install the HRMS app when upgrading to Version 14"
|
||||||
|
" to continue using the HR and Payroll modules:\n"
|
||||||
|
"https://github.com/frappe/hrms",
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
@@ -623,9 +623,20 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
def add_structure_components(self, component_type):
|
def add_structure_components(self, component_type):
|
||||||
data = self.get_data_for_eval()
|
data = self.get_data_for_eval()
|
||||||
|
timesheet_component = frappe.db.get_value(
|
||||||
|
"Salary Structure", self.salary_structure, "salary_component"
|
||||||
|
)
|
||||||
|
|
||||||
for struct_row in self._salary_structure_doc.get(component_type):
|
for struct_row in self._salary_structure_doc.get(component_type):
|
||||||
|
if self.salary_slip_based_on_timesheet and struct_row.salary_component == timesheet_component:
|
||||||
|
continue
|
||||||
|
|
||||||
amount = self.eval_condition_and_formula(struct_row, data)
|
amount = self.eval_condition_and_formula(struct_row, data)
|
||||||
if amount is not None and struct_row.statistical_component == 0:
|
if (
|
||||||
|
amount
|
||||||
|
or (struct_row.amount_based_on_formula and amount is not None)
|
||||||
|
and struct_row.statistical_component == 0
|
||||||
|
):
|
||||||
self.update_component_row(struct_row, amount, component_type, data=data)
|
self.update_component_row(struct_row, amount, component_type, data=data)
|
||||||
|
|
||||||
def get_data_for_eval(self):
|
def get_data_for_eval(self):
|
||||||
@@ -1352,23 +1363,22 @@ class SalarySlip(TransactionBase):
|
|||||||
self.total_interest_amount = 0
|
self.total_interest_amount = 0
|
||||||
self.total_principal_amount = 0
|
self.total_principal_amount = 0
|
||||||
|
|
||||||
if not self.get("loans"):
|
self.set("loans", [])
|
||||||
for loan in self.get_loan_details():
|
for loan in self.get_loan_details():
|
||||||
|
amounts = calculate_amounts(loan.name, self.posting_date, "Regular Payment")
|
||||||
|
|
||||||
amounts = calculate_amounts(loan.name, self.posting_date, "Regular Payment")
|
if amounts["interest_amount"] or amounts["payable_principal_amount"]:
|
||||||
|
self.append(
|
||||||
if amounts["interest_amount"] or amounts["payable_principal_amount"]:
|
"loans",
|
||||||
self.append(
|
{
|
||||||
"loans",
|
"loan": loan.name,
|
||||||
{
|
"total_payment": amounts["interest_amount"] + amounts["payable_principal_amount"],
|
||||||
"loan": loan.name,
|
"interest_amount": amounts["interest_amount"],
|
||||||
"total_payment": amounts["interest_amount"] + amounts["payable_principal_amount"],
|
"principal_amount": amounts["payable_principal_amount"],
|
||||||
"interest_amount": amounts["interest_amount"],
|
"loan_account": loan.loan_account,
|
||||||
"principal_amount": amounts["payable_principal_amount"],
|
"interest_income_account": loan.interest_income_account,
|
||||||
"loan_account": loan.loan_account,
|
},
|
||||||
"interest_income_account": loan.interest_income_account,
|
)
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
for payment in self.get("loans"):
|
for payment in self.get("loans"):
|
||||||
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class SalaryStructure(Document):
|
|||||||
self.validate_max_benefits_with_flexi()
|
self.validate_max_benefits_with_flexi()
|
||||||
self.validate_component_based_on_tax_slab()
|
self.validate_component_based_on_tax_slab()
|
||||||
self.validate_payment_days_based_dependent_component()
|
self.validate_payment_days_based_dependent_component()
|
||||||
|
self.validate_timesheet_component()
|
||||||
|
|
||||||
def set_missing_values(self):
|
def set_missing_values(self):
|
||||||
overwritten_fields = [
|
overwritten_fields = [
|
||||||
@@ -89,6 +90,21 @@ class SalaryStructure(Document):
|
|||||||
|
|
||||||
return abbr
|
return abbr
|
||||||
|
|
||||||
|
def validate_timesheet_component(self):
|
||||||
|
if not self.salary_slip_based_on_timesheet:
|
||||||
|
return
|
||||||
|
|
||||||
|
for component in self.earnings:
|
||||||
|
if component.salary_component == self.salary_component:
|
||||||
|
frappe.msgprint(
|
||||||
|
_(
|
||||||
|
"Row #{0}: Timesheet amount will overwrite the Earning component amount for the Salary Component {1}"
|
||||||
|
).format(self.idx, frappe.bold(self.salary_component)),
|
||||||
|
title=_("Warning"),
|
||||||
|
indicator="orange",
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
def strip_condition_and_formula_fields(self):
|
def strip_condition_and_formula_fields(self):
|
||||||
# remove whitespaces from condition and formula fields
|
# remove whitespaces from condition and formula fields
|
||||||
for row in self.earnings:
|
for row in self.earnings:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class Homepage(Document):
|
|||||||
def setup_items(self):
|
def setup_items(self):
|
||||||
for d in frappe.get_all(
|
for d in frappe.get_all(
|
||||||
"Website Item",
|
"Website Item",
|
||||||
fields=["name", "item_name", "description", "image", "route"],
|
fields=["name", "item_name", "description", "website_image", "route"],
|
||||||
filters={"published": 1},
|
filters={"published": 1},
|
||||||
limit=3,
|
limit=3,
|
||||||
):
|
):
|
||||||
@@ -31,7 +31,7 @@ class Homepage(Document):
|
|||||||
item_code=d.name,
|
item_code=d.name,
|
||||||
item_name=d.item_name,
|
item_name=d.item_name,
|
||||||
description=d.description,
|
description=d.description,
|
||||||
image=d.image,
|
image=d.website_image,
|
||||||
route=d.route,
|
route=d.route,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2122,7 +2122,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
"qty": item.qty,
|
"qty": item.qty,
|
||||||
"description": item.description,
|
"description": item.description,
|
||||||
"serial_no": item.serial_no,
|
"serial_no": item.serial_no,
|
||||||
"batch_no": item.batch_no
|
"batch_no": item.batch_no,
|
||||||
|
"sample_size": item.sample_quantity
|
||||||
});
|
});
|
||||||
dialog_items.grid.refresh();
|
dialog_items.grid.refresh();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,6 +265,10 @@ def get_overseas_address_details(address_name):
|
|||||||
def get_item_list(invoice):
|
def get_item_list(invoice):
|
||||||
item_list = []
|
item_list = []
|
||||||
|
|
||||||
|
hide_discount_in_einvoice = cint(
|
||||||
|
frappe.db.get_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice")
|
||||||
|
)
|
||||||
|
|
||||||
for d in invoice.items:
|
for d in invoice.items:
|
||||||
einvoice_item_schema = read_json("einv_item_template")
|
einvoice_item_schema = read_json("einv_item_template")
|
||||||
item = frappe._dict({})
|
item = frappe._dict({})
|
||||||
@@ -276,17 +280,12 @@ def get_item_list(invoice):
|
|||||||
item.qty = abs(item.qty)
|
item.qty = abs(item.qty)
|
||||||
item_qty = item.qty
|
item_qty = item.qty
|
||||||
|
|
||||||
item.discount_amount = abs(item.discount_amount)
|
|
||||||
item.taxable_value = abs(item.taxable_value)
|
item.taxable_value = abs(item.taxable_value)
|
||||||
|
|
||||||
if invoice.get("is_return") or invoice.get("is_debit_note"):
|
if invoice.get("is_return") or invoice.get("is_debit_note"):
|
||||||
item_qty = item_qty or 1
|
item_qty = item_qty or 1
|
||||||
|
|
||||||
hide_discount_in_einvoice = cint(
|
if hide_discount_in_einvoice or invoice.is_internal_customer or item.discount_amount < 0:
|
||||||
frappe.db.get_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice")
|
|
||||||
)
|
|
||||||
|
|
||||||
if hide_discount_in_einvoice:
|
|
||||||
item.unit_rate = item.taxable_value / item_qty
|
item.unit_rate = item.taxable_value / item_qty
|
||||||
item.gross_amount = item.taxable_value
|
item.gross_amount = item.taxable_value
|
||||||
item.discount_amount = 0
|
item.discount_amount = 0
|
||||||
|
|||||||
@@ -580,7 +580,7 @@ def get_ewb_data(dt, dn):
|
|||||||
|
|
||||||
if dt == "Delivery Note":
|
if dt == "Delivery Note":
|
||||||
data.subSupplyType = 1
|
data.subSupplyType = 1
|
||||||
elif doc.gst_category in ["Registered Regular", "SEZ"]:
|
elif doc.gst_category in ["Unregistered", "Registered Regular", "SEZ"]:
|
||||||
data.subSupplyType = 1
|
data.subSupplyType = 1
|
||||||
elif doc.gst_category in ["Overseas", "Deemed Export"]:
|
elif doc.gst_category in ["Overseas", "Deemed Export"]:
|
||||||
data.subSupplyType = 3
|
data.subSupplyType = 3
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def _execute(filters=None):
|
|||||||
added_item = []
|
added_item = []
|
||||||
for d in item_list:
|
for d in item_list:
|
||||||
if (d.parent, d.item_code) not in added_item:
|
if (d.parent, d.item_code) not in added_item:
|
||||||
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty, d.tax_rate]
|
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty, d.tax_rate or 0]
|
||||||
total_tax = 0
|
total_tax = 0
|
||||||
for tax in tax_columns:
|
for tax in tax_columns:
|
||||||
item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
|
item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
|
||||||
@@ -100,31 +100,51 @@ def get_items(filters):
|
|||||||
|
|
||||||
items = frappe.db.sql(
|
items = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
SELECT
|
||||||
`tabSales Invoice Item`.gst_hsn_code,
|
`tabSales Invoice Item`.gst_hsn_code,
|
||||||
`tabSales Invoice Item`.stock_uom,
|
`tabSales Invoice Item`.stock_uom,
|
||||||
sum(`tabSales Invoice Item`.stock_qty) as stock_qty,
|
sum(
|
||||||
sum(`tabSales Invoice Item`.base_net_amount) as base_net_amount,
|
`tabSales Invoice Item`.stock_qty
|
||||||
sum(`tabSales Invoice Item`.base_price_list_rate) as base_price_list_rate,
|
) as stock_qty,
|
||||||
|
sum(
|
||||||
|
`tabSales Invoice Item`.base_net_amount
|
||||||
|
) as base_net_amount,
|
||||||
|
sum(
|
||||||
|
`tabSales Invoice Item`.base_price_list_rate
|
||||||
|
) as base_price_list_rate,
|
||||||
|
|
||||||
`tabSales Invoice Item`.parent,
|
`tabSales Invoice Item`.parent,
|
||||||
`tabSales Invoice Item`.item_code,
|
`tabSales Invoice Item`.item_code,
|
||||||
`tabGST HSN Code`.description,
|
`tabGST HSN Code`.description,
|
||||||
json_extract(`tabSales Taxes and Charges`.item_wise_tax_detail,
|
json_extract(
|
||||||
concat('$."' , `tabSales Invoice Item`.item_code, '"[0]')) * count(distinct `tabSales Taxes and Charges`.name) as tax_rate
|
`tabSales Taxes and Charges`.item_wise_tax_detail,
|
||||||
from
|
concat(
|
||||||
`tabSales Invoice`,
|
'$."', `tabSales Invoice Item`.item_code,
|
||||||
`tabSales Invoice Item`,
|
'"[0]'
|
||||||
`tabGST HSN Code`,
|
)
|
||||||
`tabSales Taxes and Charges`
|
) * count(
|
||||||
where
|
distinct `tabSales Taxes and Charges`.name
|
||||||
`tabSales Invoice`.name = `tabSales Invoice Item`.parent
|
) as tax_rate
|
||||||
and `tabSales Taxes and Charges`.parent = `tabSales Invoice`.name
|
FROM
|
||||||
and `tabSales Invoice`.docstatus = 1
|
`tabSales Invoice`
|
||||||
and `tabSales Invoice Item`.gst_hsn_code is not NULL
|
INNER JOIN
|
||||||
and `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name %s %s
|
`tabSales Invoice Item` ON `tabSales Invoice`.name = `tabSales Invoice Item`.parent
|
||||||
group by
|
INNER JOIN
|
||||||
|
`tabGST HSN Code` ON `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name % s % s
|
||||||
|
LEFT JOIN
|
||||||
|
`tabSales Taxes and Charges` ON `tabSales Taxes and Charges`.parent = `tabSales Invoice`.name
|
||||||
|
WHERE
|
||||||
|
`tabSales Invoice`.docstatus = 1
|
||||||
|
AND
|
||||||
|
`tabSales Invoice Item`.gst_hsn_code is not NULL
|
||||||
|
GROUP BY
|
||||||
`tabSales Invoice Item`.parent,
|
`tabSales Invoice Item`.parent,
|
||||||
`tabSales Invoice Item`.item_code
|
`tabSales Invoice Item`.item_code,
|
||||||
|
`tabSales Invoice Item`.gst_hsn_code,
|
||||||
|
`tabSales Invoice Item`.uom
|
||||||
|
ORDER BY
|
||||||
|
`tabSales Invoice Item`.gst_hsn_code,
|
||||||
|
`tabSales Invoice Item`.uom
|
||||||
"""
|
"""
|
||||||
% (conditions, match_conditions),
|
% (conditions, match_conditions),
|
||||||
filters,
|
filters,
|
||||||
|
|||||||
@@ -1548,6 +1548,65 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
so.load_from_db()
|
so.load_from_db()
|
||||||
self.assertEqual(so.billing_status, "Fully Billed")
|
self.assertEqual(so.billing_status, "Fully Billed")
|
||||||
|
|
||||||
|
def test_so_billing_status_with_crnote_against_sales_return(self):
|
||||||
|
"""
|
||||||
|
| Step | Document creation | |
|
||||||
|
|------+--------------------------------------+-------------------------------|
|
||||||
|
| 1 | SO -> DN -> SI | SO Fully Billed and Completed |
|
||||||
|
| 2 | DN -> Sales Return(Partial) | SO 50% Delivered, 100% billed |
|
||||||
|
| 3 | Sales Return(Partial) -> Credit Note | SO 50% Delivered, 50% billed |
|
||||||
|
|
||||||
|
"""
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
|
so = make_sales_order(uom="Nos", do_not_save=1)
|
||||||
|
so.save()
|
||||||
|
so.submit()
|
||||||
|
|
||||||
|
self.assertEqual(so.billing_status, "Not Billed")
|
||||||
|
|
||||||
|
dn1 = make_delivery_note(so.name)
|
||||||
|
dn1.taxes_and_charges = ""
|
||||||
|
dn1.taxes.clear()
|
||||||
|
dn1.save().submit()
|
||||||
|
|
||||||
|
si = create_sales_invoice(qty=10, do_not_save=1)
|
||||||
|
si.items[0].sales_order = so.name
|
||||||
|
si.items[0].so_detail = so.items[0].name
|
||||||
|
si.items[0].delivery_note = dn1.name
|
||||||
|
si.items[0].dn_detail = dn1.items[0].name
|
||||||
|
si.save()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.billing_status, "Fully Billed")
|
||||||
|
self.assertEqual(so.status, "Completed")
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
|
dn1.reload()
|
||||||
|
dn_ret = create_delivery_note(is_return=1, return_against=dn1.name, qty=-5, do_not_submit=True)
|
||||||
|
dn_ret.items[0].against_sales_order = so.name
|
||||||
|
dn_ret.items[0].so_detail = so.items[0].name
|
||||||
|
dn_ret.submit()
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.per_billed, 100)
|
||||||
|
self.assertEqual(so.per_delivered, 50)
|
||||||
|
|
||||||
|
cr_note = create_sales_invoice(is_return=1, qty=-1, do_not_submit=True)
|
||||||
|
cr_note.items[0].qty = -5
|
||||||
|
cr_note.items[0].sales_order = so.name
|
||||||
|
cr_note.items[0].so_detail = so.items[0].name
|
||||||
|
cr_note.items[0].delivery_note = dn_ret.name
|
||||||
|
cr_note.items[0].dn_detail = dn_ret.items[0].name
|
||||||
|
cr_note.update_billed_amount_in_sales_order = True
|
||||||
|
cr_note.submit()
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.per_billed, 50)
|
||||||
|
self.assertEqual(so.per_delivered, 50)
|
||||||
|
|
||||||
def test_so_back_updated_from_wo_via_mr(self):
|
def test_so_back_updated_from_wo_via_mr(self):
|
||||||
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
|
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order import (
|
from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||||
|
|||||||
@@ -497,7 +497,10 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
|
|
||||||
set_pos_profile_data() {
|
set_pos_profile_data() {
|
||||||
if (this.company && !this.frm.doc.company) this.frm.doc.company = this.company;
|
if (this.company && !this.frm.doc.company) this.frm.doc.company = this.company;
|
||||||
if (this.pos_profile && !this.frm.doc.pos_profile) this.frm.doc.pos_profile = this.pos_profile;
|
if ((this.pos_profile && !this.frm.doc.pos_profile) | (this.frm.doc.is_return && this.pos_profile != this.frm.doc.pos_profile)) {
|
||||||
|
this.frm.doc.pos_profile = this.pos_profile;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.frm.doc.company) return;
|
if (!this.frm.doc.company) return;
|
||||||
|
|
||||||
return this.frm.trigger("set_pos_data");
|
return this.frm.trigger("set_pos_data");
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"company",
|
"company",
|
||||||
"purpose",
|
"purpose",
|
||||||
"customer",
|
"customer",
|
||||||
|
"customer_name",
|
||||||
"work_order",
|
"work_order",
|
||||||
"material_request",
|
"material_request",
|
||||||
"for_qty",
|
"for_qty",
|
||||||
@@ -126,11 +127,19 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Group Same Items",
|
"label": "Group Same Items",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.purpose==='Delivery' && doc.customer",
|
||||||
|
"fetch_from": "customer.customer_name",
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Customer Name",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-04-21 07:56:40.646473",
|
"modified": "2022-07-19 11:03:04.442174",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Pick List",
|
"name": "Pick List",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ product_image(doc.website_image or doc.image, alt=doc.website_image_alt or doc.item_name) }}
|
{{ product_image(doc.website_image, alt=doc.website_image_alt or doc.item_name) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Simple image preview -->
|
<!-- Simple image preview -->
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
{%- set col_size = 3 if is_full_width else 4 -%}
|
{%- set col_size = 3 if is_full_width else 4 -%}
|
||||||
{%- set title = item.web_item_name or item.item_name or item.item_code -%}
|
{%- set title = item.web_item_name or item.item_name or item.item_code -%}
|
||||||
{%- set title = title[:50] + "..." if title|len > 50 else title -%}
|
{%- set title = title[:50] + "..." if title|len > 50 else title -%}
|
||||||
{%- set image = item.website_image or item.image -%}
|
{%- set image = item.website_image -%}
|
||||||
{%- set description = item.website_description or item.description-%}
|
{%- set description = item.website_description or item.description-%}
|
||||||
|
|
||||||
{% if is_featured %}
|
{% if is_featured %}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/* csslint ignore:start */
|
/* csslint ignore:start */
|
||||||
{% if homepage.hero_image %}
|
{% if homepage.hero_image %}
|
||||||
.hero-image {
|
.hero-image {
|
||||||
background-image: url("{{ homepage.hero_image }}");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
padding: 10rem 0;
|
padding: 10rem 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
{% if homepage.hero_section_based_on == 'Default' %}
|
{% if homepage.hero_section_based_on == 'Default' %}
|
||||||
<section class="hero-section border-bottom {%if homepage.hero_image%}hero-image{%endif%}">
|
<section class="hero-section border-bottom {%if homepage.hero_image%}hero-image{%endif%}"
|
||||||
|
{% if homepage.hero_image %}
|
||||||
|
style="background-image: url('{{ homepage.hero_image }}');"
|
||||||
|
{%- endif %}
|
||||||
|
>
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<h1 class="d-none d-sm-block display-4">{{ homepage.tag_line }}</h1>
|
<h1 class="d-none d-sm-block display-4">{{ homepage.tag_line }}</h1>
|
||||||
<h1 class="d-block d-sm-none">{{ homepage.tag_line }}</h1>
|
<h1 class="d-block d-sm-none">{{ homepage.tag_line }}</h1>
|
||||||
|
|||||||
@@ -5997,7 +5997,7 @@ CN,CN,
|
|||||||
DE,DE,
|
DE,DE,
|
||||||
ES,ES,
|
ES,ES,
|
||||||
FR,FR,
|
FR,FR,
|
||||||
IN,IM,
|
IN,Ein,
|
||||||
JP,JP,
|
JP,JP,
|
||||||
IT,ES,
|
IT,ES,
|
||||||
MX,MX,
|
MX,MX,
|
||||||
|
|||||||
|
Can't render this file because it is too large.
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user