Merge remote-tracking branch 'upstream/develop' into fix-reserve-qty
This commit is contained in:
commit
1d1c975da1
@ -234,17 +234,19 @@ def get_checks_for_pl_and_bs_accounts():
|
||||
return dimensions
|
||||
|
||||
|
||||
def get_dimension_with_children(doctype, dimension):
|
||||
def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
if isinstance(dimension, list):
|
||||
dimension = dimension[0]
|
||||
if isinstance(dimensions, str):
|
||||
dimensions = [dimensions]
|
||||
|
||||
all_dimensions = []
|
||||
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
|
||||
children = frappe.get_all(
|
||||
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
|
||||
)
|
||||
all_dimensions += [c.name for c in children]
|
||||
|
||||
for dimension in dimensions:
|
||||
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
|
||||
children = frappe.get_all(
|
||||
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
|
||||
)
|
||||
all_dimensions += [c.name for c in children]
|
||||
|
||||
return all_dimensions
|
||||
|
||||
|
@ -507,7 +507,7 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||
)
|
||||
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
else:
|
||||
additional_conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
|
||||
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
|
||||
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
|
||||
|
||||
|
@ -275,7 +275,7 @@ def get_conditions(filters):
|
||||
)
|
||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
else:
|
||||
conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
|
||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
|
@ -237,7 +237,7 @@ def get_conditions(filters):
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabPurchase Invoice Item`.{0}, '') in (%({0})s))".format(dimension.fieldname)
|
||||
+ "and ifnull(`tabPurchase Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
|
@ -405,7 +405,7 @@ def get_conditions(filters):
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabSales Invoice Item`.{0}, '') in (%({0})s))".format(dimension.fieldname)
|
||||
+ "and ifnull(`tabSales Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
|
@ -188,9 +188,9 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
additional_conditions += "and {0} in %({0})s".format(dimension.fieldname)
|
||||
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
|
||||
else:
|
||||
additional_conditions += "and {0} in (%({0})s)".format(dimension.fieldname)
|
||||
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
|
||||
|
||||
query_filters.update({dimension.fieldname: filters.get(dimension.fieldname)})
|
||||
|
||||
|
@ -148,6 +148,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
self.validate_inter_company_reference()
|
||||
|
||||
self.disable_pricing_rule_on_internal_transfer()
|
||||
self.set_incoming_rate()
|
||||
|
||||
if self.meta.get_field("currency"):
|
||||
@ -382,6 +383,14 @@ class AccountsController(TransactionBase):
|
||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||
|
||||
def disable_pricing_rule_on_internal_transfer(self):
|
||||
if not self.get("ignore_pricing_rule") and self.is_internal_transfer():
|
||||
self.ignore_pricing_rule = 1
|
||||
frappe.msgprint(
|
||||
_("Disabled pricing rules since this {} is an internal transfer").format(self.doctype),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
def validate_due_date(self):
|
||||
if self.get("is_pos"):
|
||||
return
|
||||
@ -1738,6 +1747,8 @@ class AccountsController(TransactionBase):
|
||||
internal_party_field = "is_internal_customer"
|
||||
elif self.doctype in ("Purchase Invoice", "Purchase Receipt", "Purchase Order"):
|
||||
internal_party_field = "is_internal_supplier"
|
||||
else:
|
||||
return False
|
||||
|
||||
if self.get(internal_party_field) and (self.represents_company == self.company):
|
||||
return True
|
||||
|
@ -307,14 +307,15 @@ class BuyingController(StockController, Subcontracting):
|
||||
if self.is_internal_transfer():
|
||||
if rate != d.rate:
|
||||
d.rate = rate
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
supplied_items_cost = 0.0
|
||||
|
@ -447,15 +447,16 @@ class SellingController(StockController):
|
||||
rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
|
||||
if d.rate != rate:
|
||||
d.rate = rate
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
|
||||
elif self.get("return_against"):
|
||||
# Get incoming rate of return entry from reference document
|
||||
|
@ -129,6 +129,7 @@ class TestShoppingCart(unittest.TestCase):
|
||||
self.assertEqual(quotation.net_total, 20)
|
||||
self.assertEqual(len(quotation.get("items")), 1)
|
||||
|
||||
@unittest.skip("Flaky in CI")
|
||||
def test_tax_rule(self):
|
||||
self.create_tax_rule()
|
||||
self.login_as_customer()
|
||||
|
@ -321,6 +321,7 @@ doc_events = {
|
||||
"validate": [
|
||||
"erpnext.regional.india.utils.validate_document_name",
|
||||
"erpnext.regional.india.utils.update_taxable_values",
|
||||
"erpnext.regional.india.utils.validate_sez_and_export_invoices",
|
||||
],
|
||||
},
|
||||
"POS Invoice": {"on_submit": ["erpnext.regional.saudi_arabia.utils.create_qr_code"]},
|
||||
|
@ -227,11 +227,15 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
|
||||
in_log = out_log = None
|
||||
if not in_log:
|
||||
in_log = log if log.log_type == "IN" else None
|
||||
if in_log and not in_time:
|
||||
in_time = in_log.time
|
||||
elif not out_log:
|
||||
out_log = log if log.log_type == "OUT" else None
|
||||
|
||||
if in_log and out_log:
|
||||
out_time = out_log.time
|
||||
total_hours += time_diff_in_hours(in_log.time, out_log.time)
|
||||
|
||||
return total_hours, in_time, out_time
|
||||
|
||||
|
||||
|
@ -125,6 +125,11 @@ class TestEmployeeCheckin(FrappeTestCase):
|
||||
)
|
||||
self.assertEqual(working_hours, (4.5, logs_type_2[1].time, logs_type_2[-1].time))
|
||||
|
||||
working_hours = calculate_working_hours(
|
||||
[logs_type_2[1], logs_type_2[-1]], check_in_out_type[1], working_hours_calc_type[1]
|
||||
)
|
||||
self.assertEqual(working_hours, (5.0, logs_type_2[1].time, logs_type_2[-1].time))
|
||||
|
||||
def test_fetch_shift(self):
|
||||
employee = make_employee("test_employee_checkin@example.com", company="_Test Company")
|
||||
|
||||
|
@ -264,6 +264,7 @@ class LoanRepayment(AccountsController):
|
||||
regenerate_repayment_schedule(self.against_loan, cancel)
|
||||
|
||||
def allocate_amounts(self, repayment_details):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
self.set("repayment_details", [])
|
||||
self.principal_amount_paid = 0
|
||||
self.total_penalty_paid = 0
|
||||
@ -278,9 +279,9 @@ class LoanRepayment(AccountsController):
|
||||
|
||||
if interest_paid > 0:
|
||||
if self.penalty_amount and interest_paid > self.penalty_amount:
|
||||
self.total_penalty_paid = self.penalty_amount
|
||||
self.total_penalty_paid = flt(self.penalty_amount, precision)
|
||||
elif self.penalty_amount:
|
||||
self.total_penalty_paid = interest_paid
|
||||
self.total_penalty_paid = flt(interest_paid, precision)
|
||||
|
||||
interest_paid -= self.total_penalty_paid
|
||||
|
||||
|
@ -367,7 +367,8 @@ erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances
|
||||
erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
|
||||
erpnext.patches.v13_0.copy_custom_field_filters_to_website_item
|
||||
erpnext.patches.v13_0.change_default_item_manufacturer_fieldtype
|
||||
erpnext.patches.v13_0.requeue_recoverable_reposts
|
||||
erpnext.patches.v14_0.discount_accounting_separation
|
||||
erpnext.patches.v14_0.delete_employee_transfer_property_doctype
|
||||
erpnext.patches.v13_0.create_accounting_dimensions_in_orders
|
||||
erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
|
||||
erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
|
||||
|
21
erpnext/patches/v13_0/requeue_recoverable_reposts.py
Normal file
21
erpnext/patches/v13_0/requeue_recoverable_reposts.py
Normal file
@ -0,0 +1,21 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
recoverable = ("QueryDeadlockError", "QueryTimeoutError", "JobTimeoutException")
|
||||
|
||||
failed_reposts = frappe.get_all(
|
||||
"Repost Item Valuation",
|
||||
fields=["name", "error_log"],
|
||||
filters={
|
||||
"status": "Failed",
|
||||
"docstatus": 1,
|
||||
"modified": (">", "2022-04-20"),
|
||||
"error_log": ("is", "set"),
|
||||
},
|
||||
)
|
||||
for riv in failed_reposts:
|
||||
for exc in recoverable:
|
||||
if exc in riv.error_log:
|
||||
frappe.db.set_value("Repost Item Valuation", riv.name, "status", "Queued")
|
||||
break
|
@ -90,7 +90,7 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac
|
||||
else {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1 }
|
||||
filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1, 'has_variants': 0}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -213,8 +213,10 @@ $.extend(erpnext.utils, {
|
||||
filters.splice(index, 0, {
|
||||
"fieldname": dimension["fieldname"],
|
||||
"label": __(dimension["label"]),
|
||||
"fieldtype": "Link",
|
||||
"options": dimension["document_type"]
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options(dimension["document_type"], txt);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -31,30 +31,39 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
}
|
||||
|
||||
process_scan() {
|
||||
let me = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
let me = this;
|
||||
|
||||
const input = this.scan_barcode_field.value;
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
const input = this.scan_barcode_field.value;
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
frappe
|
||||
.call({
|
||||
method: this.scan_api,
|
||||
args: {
|
||||
search_value: input,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
const data = r && r.message;
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
this.show_alert(__("Cannot find Item with this Barcode"), "red");
|
||||
this.clean_up();
|
||||
return;
|
||||
}
|
||||
frappe
|
||||
.call({
|
||||
method: this.scan_api,
|
||||
args: {
|
||||
search_value: input,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
const data = r && r.message;
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
this.show_alert(__("Cannot find Item with this Barcode"), "red");
|
||||
this.clean_up();
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
me.update_table(data);
|
||||
});
|
||||
const row = me.update_table(data);
|
||||
if (row) {
|
||||
resolve(row);
|
||||
}
|
||||
else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
update_table(data) {
|
||||
@ -90,6 +99,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
this.set_batch_no(row, batch_no);
|
||||
this.set_barcode(row, barcode);
|
||||
this.clean_up();
|
||||
return row;
|
||||
}
|
||||
|
||||
// batch and serial selector is reduandant when all info can be added by scan
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:hsn_code",
|
||||
"creation": "2017-06-21 10:48:56.422086",
|
||||
"doctype": "DocType",
|
||||
@ -7,6 +8,7 @@
|
||||
"field_order": [
|
||||
"hsn_code",
|
||||
"description",
|
||||
"gst_rates",
|
||||
"taxes"
|
||||
],
|
||||
"fields": [
|
||||
@ -16,22 +18,37 @@
|
||||
"in_list_view": 1,
|
||||
"label": "HSN Code",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
"label": "Description",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Taxes",
|
||||
"options": "Item Tax"
|
||||
"options": "Item Tax",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "gst_rates",
|
||||
"fieldtype": "Table",
|
||||
"label": "GST Rates",
|
||||
"options": "HSN Tax Rate",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"modified": "2019-11-01 11:18:59.556931",
|
||||
"links": [],
|
||||
"modified": "2022-05-11 13:42:27.286643",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "GST HSN Code",
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
frappe.ui.form.on('GST Settings', {
|
||||
refresh: function(frm) {
|
||||
frm.add_custom_button('Send GST Update Reminder', () => {
|
||||
frm.add_custom_button(__('Send GST Update Reminder'), () => {
|
||||
return new Promise((resolve) => {
|
||||
return frappe.call({
|
||||
method: 'erpnext.regional.doctype.gst_settings.gst_settings.send_reminder'
|
||||
@ -11,6 +11,12 @@ frappe.ui.form.on('GST Settings', {
|
||||
});
|
||||
});
|
||||
|
||||
frm.add_custom_button(__('Sync HSN Codes'), () => {
|
||||
frappe.call({
|
||||
"method": "erpnext.regional.doctype.gst_settings.gst_settings.update_hsn_codes"
|
||||
});
|
||||
});
|
||||
|
||||
$(frm.fields_dict.gst_summary.wrapper).empty().html(
|
||||
`<table class="table table-bordered">
|
||||
<tbody>
|
||||
|
@ -2,13 +2,14 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.contacts.doctype.contact.contact import get_default_contact
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import date_diff, get_url, nowdate
|
||||
from frappe.utils import date_diff, flt, get_url, nowdate
|
||||
|
||||
|
||||
class EmailMissing(frappe.ValidationError):
|
||||
@ -129,3 +130,31 @@ def _send_gstin_reminder(party_type, party, default_email_id=None, sent_to=None)
|
||||
)
|
||||
|
||||
return email_id
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_hsn_codes():
|
||||
frappe.enqueue(enqueue_update)
|
||||
frappe.msgprint(_("HSN/SAC Code sync started, this may take a few minutes..."))
|
||||
|
||||
|
||||
def enqueue_update():
|
||||
with open(os.path.join(os.path.dirname(__file__), "hsn_code_data.json"), "r") as f:
|
||||
hsn_codes = json.loads(f.read())
|
||||
|
||||
for hsn_code in hsn_codes:
|
||||
try:
|
||||
hsn_code_doc = frappe.get_doc("GST HSN Code", hsn_code.get("hsn_code"))
|
||||
hsn_code_doc.set("gst_rates", [])
|
||||
for rate in hsn_code.get("gst_rates"):
|
||||
hsn_code_doc.append(
|
||||
"gst_rates",
|
||||
{
|
||||
"minimum_taxable_value": flt(hsn_code.get("minimum_taxable_value")),
|
||||
"tax_rate": flt(rate.get("tax_rate")),
|
||||
},
|
||||
)
|
||||
|
||||
hsn_code_doc.save()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
144352
erpnext/regional/doctype/gst_settings/hsn_code_data.json
Normal file
144352
erpnext/regional/doctype/gst_settings/hsn_code_data.json
Normal file
File diff suppressed because it is too large
Load Diff
0
erpnext/regional/doctype/hsn_tax_rate/__init__.py
Normal file
0
erpnext/regional/doctype/hsn_tax_rate/__init__.py
Normal file
39
erpnext/regional/doctype/hsn_tax_rate/hsn_tax_rate.json
Normal file
39
erpnext/regional/doctype/hsn_tax_rate/hsn_tax_rate.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2022-05-11 13:32:42.534779",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"minimum_taxable_value",
|
||||
"tax_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "minimum_taxable_value",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Minimum Taxable Value"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "tax_rate",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Rate"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-15 15:37:56.152470",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "HSN Tax Rate",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
9
erpnext/regional/doctype/hsn_tax_rate/hsn_tax_rate.py
Normal file
9
erpnext/regional/doctype/hsn_tax_rate/hsn_tax_rate.py
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class HSNTaxRate(Document):
|
||||
pass
|
@ -840,6 +840,30 @@ def get_gst_accounts(
|
||||
return gst_accounts
|
||||
|
||||
|
||||
def validate_sez_and_export_invoices(doc, method):
|
||||
country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
|
||||
if country != "India":
|
||||
return
|
||||
|
||||
if (
|
||||
doc.get("gst_category") in ("SEZ", "Overseas")
|
||||
and doc.get("export_type") == "Without Payment of Tax"
|
||||
):
|
||||
gst_accounts = get_gst_accounts(doc.company)
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
for tax in doc.get("taxes"):
|
||||
if (
|
||||
tax.account_head
|
||||
in gst_accounts.get("igst_account", [])
|
||||
+ gst_accounts.get("sgst_account", [])
|
||||
+ gst_accounts.get("cgst_account", [])
|
||||
and tax.tax_amount_after_discount_amount
|
||||
):
|
||||
frappe.throw(_("GST cannot be applied on SEZ or Export invoices without payment of tax"))
|
||||
|
||||
|
||||
def validate_reverse_charge_transaction(doc, method):
|
||||
country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
|
||||
@ -887,6 +911,8 @@ def validate_reverse_charge_transaction(doc, method):
|
||||
|
||||
frappe.throw(msg)
|
||||
|
||||
doc.eligibility_for_itc = "ITC on Reverse Charge"
|
||||
|
||||
|
||||
def update_itc_availed_fields(doc, method):
|
||||
country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
|
@ -226,7 +226,10 @@ class Gstr1Report(object):
|
||||
taxable_value += abs(net_amount)
|
||||
elif (
|
||||
not tax_rate
|
||||
and self.filters.get("type_of_business") == "EXPORT"
|
||||
and (
|
||||
self.filters.get("type_of_business") == "EXPORT"
|
||||
or invoice_details.get("gst_category") == "SEZ"
|
||||
)
|
||||
and invoice_details.get("export_type") == "Without Payment of Tax"
|
||||
):
|
||||
taxable_value += abs(net_amount)
|
||||
@ -328,12 +331,14 @@ class Gstr1Report(object):
|
||||
def get_invoice_items(self):
|
||||
self.invoice_items = frappe._dict()
|
||||
self.item_tax_rate = frappe._dict()
|
||||
self.item_hsn_map = frappe._dict()
|
||||
self.nil_exempt_non_gst = {}
|
||||
|
||||
# nosemgrep
|
||||
items = frappe.db.sql(
|
||||
"""
|
||||
select item_code, parent, taxable_value, base_net_amount, item_tax_rate, is_nil_exempt,
|
||||
is_non_gst from `tab%s Item`
|
||||
gst_hsn_code, is_non_gst from `tab%s Item`
|
||||
where parent in (%s)
|
||||
"""
|
||||
% (self.doctype, ", ".join(["%s"] * len(self.invoices))),
|
||||
@ -343,6 +348,7 @@ class Gstr1Report(object):
|
||||
|
||||
for d in items:
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
|
||||
self.item_hsn_map.setdefault(d.item_code, d.gst_hsn_code)
|
||||
self.invoice_items[d.parent][d.item_code] += d.get("taxable_value", 0) or d.get(
|
||||
"base_net_amount", 0
|
||||
)
|
||||
@ -367,6 +373,8 @@ class Gstr1Report(object):
|
||||
self.nil_exempt_non_gst[d.parent][2] += d.get("taxable_value", 0)
|
||||
|
||||
def get_items_based_on_tax_rate(self):
|
||||
hsn_wise_tax_rate = get_hsn_wise_tax_rates()
|
||||
|
||||
self.tax_details = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
@ -427,7 +435,7 @@ class Gstr1Report(object):
|
||||
alert=True,
|
||||
)
|
||||
|
||||
# Build itemised tax for export invoices where tax table is blank
|
||||
# Build itemised tax for export invoices where tax table is blank (Export and SEZ Invoices)
|
||||
for invoice, items in self.invoice_items.items():
|
||||
if (
|
||||
invoice not in self.items_based_on_tax_rate
|
||||
@ -435,7 +443,17 @@ class Gstr1Report(object):
|
||||
and self.invoices.get(invoice, {}).get("export_type") == "Without Payment of Tax"
|
||||
and self.invoices.get(invoice, {}).get("gst_category") in ("Overseas", "SEZ")
|
||||
):
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {})
|
||||
for item_code in items.keys():
|
||||
hsn_code = self.item_hsn_map.get(item_code)
|
||||
tax_rate = 0
|
||||
taxable_value = items.get(item_code)
|
||||
for rates in hsn_wise_tax_rate.get(hsn_code):
|
||||
if taxable_value > rates.get("minimum_taxable_value"):
|
||||
tax_rate = rates.get("tax_rate")
|
||||
|
||||
self.items_based_on_tax_rate[invoice].setdefault(tax_rate, [])
|
||||
self.items_based_on_tax_rate[invoice][tax_rate].append(item_code)
|
||||
|
||||
def get_columns(self):
|
||||
self.other_columns = []
|
||||
@ -728,7 +746,7 @@ def get_json(filters, report_name, data):
|
||||
|
||||
elif filters["type_of_business"] == "EXPORT":
|
||||
for item in report_data[:-1]:
|
||||
res.setdefault(item["export_type"], []).append(item)
|
||||
res.setdefault(item["export_type"], {}).setdefault(item["invoice_number"], []).append(item)
|
||||
|
||||
out = get_export_json(res)
|
||||
gst_json["exp"] = out
|
||||
@ -918,11 +936,21 @@ def get_export_json(res):
|
||||
for exp_type in res:
|
||||
exp_item, inv = {"exp_typ": exp_type, "inv": []}, []
|
||||
|
||||
for row in res[exp_type]:
|
||||
inv_item = get_basic_invoice_detail(row)
|
||||
inv_item["itms"] = [
|
||||
{"txval": flt(row["taxable_value"], 2), "rt": row["rate"] or 0, "iamt": 0, "csamt": 0}
|
||||
]
|
||||
for number, invoice in res[exp_type].items():
|
||||
inv_item = get_basic_invoice_detail(invoice[0])
|
||||
inv_item["itms"] = []
|
||||
|
||||
for item in invoice:
|
||||
inv_item["itms"].append(
|
||||
{
|
||||
"txval": flt(item["taxable_value"], 2),
|
||||
"rt": flt(item["rate"]),
|
||||
"iamt": flt((item["taxable_value"] * flt(item["rate"])) / 100.0, 2)
|
||||
if exp_type != "WOPAY"
|
||||
else 0,
|
||||
"csamt": (flt(item.get("cess_amount"), 2) or 0),
|
||||
}
|
||||
)
|
||||
|
||||
inv.append(inv_item)
|
||||
|
||||
@ -1060,7 +1088,6 @@ def get_rate_and_tax_details(row, gstin):
|
||||
|
||||
# calculate tax amount added
|
||||
tax = flt((row["taxable_value"] * rate) / 100.0, 2)
|
||||
frappe.errprint([tax, tax / 2])
|
||||
if row.get("billing_address_gstin") and gstin[0:2] == row["billing_address_gstin"][0:2]:
|
||||
itm_det.update({"camt": flt(tax / 2.0, 2), "samt": flt(tax / 2.0, 2)})
|
||||
else:
|
||||
@ -1136,3 +1163,26 @@ def get_company_gstins(company):
|
||||
address_list = [""] + [d.gstin for d in addresses]
|
||||
|
||||
return address_list
|
||||
|
||||
|
||||
def get_hsn_wise_tax_rates():
|
||||
hsn_wise_tax_rate = {}
|
||||
gst_hsn_code = frappe.qb.DocType("GST HSN Code")
|
||||
hsn_tax_rates = frappe.qb.DocType("HSN Tax Rate")
|
||||
|
||||
hsn_code_data = (
|
||||
frappe.qb.from_(gst_hsn_code)
|
||||
.inner_join(hsn_tax_rates)
|
||||
.on(gst_hsn_code.name == hsn_tax_rates.parent)
|
||||
.select(gst_hsn_code.hsn_code, hsn_tax_rates.tax_rate, hsn_tax_rates.minimum_taxable_value)
|
||||
.orderby(hsn_tax_rates.minimum_taxable_value)
|
||||
.run(as_dict=1)
|
||||
)
|
||||
|
||||
for d in hsn_code_data:
|
||||
hsn_wise_tax_rate.setdefault(d.hsn_code, [])
|
||||
hsn_wise_tax_rate[d.hsn_code].append(
|
||||
{"minimum_taxable_value": d.minimum_taxable_value, "tax_rate": d.tax_rate}
|
||||
)
|
||||
|
||||
return hsn_wise_tax_rate
|
||||
|
@ -221,7 +221,7 @@ def get_merged_data(columns, data):
|
||||
result = []
|
||||
|
||||
for row in data:
|
||||
key = row[0] + "-" + str(row[4])
|
||||
key = row[0] + "-" + row[2] + "-" + str(row[4])
|
||||
merged_hsn_dict.setdefault(key, {})
|
||||
for i, d in enumerate(columns):
|
||||
if d["fieldtype"] not in ("Int", "Float", "Currency"):
|
||||
|
@ -367,7 +367,14 @@ def set_credit_limit(customer, company, credit_limit):
|
||||
customer.credit_limits[-1].db_insert()
|
||||
|
||||
|
||||
def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
|
||||
def create_internal_customer(
|
||||
customer_name=None, represents_company=None, allowed_to_interact_with=None
|
||||
):
|
||||
if not customer_name:
|
||||
customer_name = represents_company
|
||||
if not allowed_to_interact_with:
|
||||
allowed_to_interact_with = represents_company
|
||||
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
customer = frappe.get_doc(
|
||||
{
|
||||
|
@ -64,7 +64,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
|
||||
this.frm.set_query("item_code", "items", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer}
|
||||
filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer, 'has_variants': 0}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -570,15 +570,12 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
customer=customer_name,
|
||||
cost_center="Main - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
do_not_submit=True,
|
||||
qty=5,
|
||||
rate=500,
|
||||
warehouse="Stores - TCP1",
|
||||
target_warehouse=target_warehouse,
|
||||
)
|
||||
|
||||
dn.submit()
|
||||
|
||||
# qty after delivery
|
||||
actual_qty_at_source = get_qty_after_transaction(warehouse="Stores - TCP1")
|
||||
self.assertEqual(actual_qty_at_source, 475)
|
||||
@ -1000,6 +997,73 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
self.assertEqual(dn2.items[0].returned_qty, 0)
|
||||
self.assertEqual(dn2.per_billed, 100)
|
||||
|
||||
def test_internal_transfer_with_valuation_only(self):
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
|
||||
item = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
target = "Stores - _TC"
|
||||
company = "_Test Company"
|
||||
customer = create_internal_customer(represents_company=company)
|
||||
rate = 42
|
||||
|
||||
# Create item price and pricing rule
|
||||
frappe.get_doc(
|
||||
{
|
||||
"item_code": item,
|
||||
"price_list": "Standard Selling",
|
||||
"price_list_rate": 1000,
|
||||
"doctype": "Item Price",
|
||||
}
|
||||
).insert()
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pricing Rule",
|
||||
"title": frappe.generate_hash(),
|
||||
"apply_on": "Item Code",
|
||||
"price_or_product_discount": "Price",
|
||||
"selling": 1,
|
||||
"company": company,
|
||||
"margin_type": "Percentage",
|
||||
"margin_rate_or_amount": 10,
|
||||
"apply_discount_on": "Grand Total",
|
||||
"items": [
|
||||
{
|
||||
"item_code": item,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert()
|
||||
|
||||
make_stock_entry(target=warehouse, qty=5, basic_rate=rate, item_code=item)
|
||||
dn = create_delivery_note(
|
||||
item_code=item,
|
||||
company=company,
|
||||
customer=customer,
|
||||
qty=5,
|
||||
rate=500,
|
||||
warehouse=warehouse,
|
||||
target_warehouse=target,
|
||||
ignore_pricing_rule=0,
|
||||
do_not_save=True,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
self.assertEqual(dn.items[0].rate, 500) # haven't saved yet
|
||||
dn.save()
|
||||
self.assertEqual(dn.ignore_pricing_rule, 1)
|
||||
|
||||
# rate should reset to incoming rate
|
||||
self.assertEqual(dn.items[0].rate, rate)
|
||||
|
||||
# rate should reset again if discounts are fiddled with
|
||||
dn.items[0].margin_type = "Amount"
|
||||
dn.items[0].margin_rate_or_amount = 50
|
||||
dn.save()
|
||||
|
||||
self.assertEqual(dn.items[0].rate, rate)
|
||||
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
|
@ -586,8 +586,7 @@ $.extend(erpnext.item, {
|
||||
["parent","=", d.attribute]
|
||||
],
|
||||
fields: ["attribute_value"],
|
||||
limit_start: 0,
|
||||
limit_page_length: 500,
|
||||
limit_page_length: 0,
|
||||
parent: "Item Attribute",
|
||||
order_by: "idx"
|
||||
}
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime
|
||||
from frappe.utils.user import get_users_with_role
|
||||
from rq.timeouts import JobTimeoutException
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_future_stock_vouchers, repost_gle_for_stock_vouchers
|
||||
@ -15,6 +17,8 @@ from erpnext.stock.stock_ledger import (
|
||||
repost_future_sle,
|
||||
)
|
||||
|
||||
RecoverableErrors = (JobTimeoutException, QueryDeadlockError, QueryTimeoutError)
|
||||
|
||||
|
||||
class RepostItemValuation(Document):
|
||||
def validate(self):
|
||||
@ -132,7 +136,7 @@ def repost(doc):
|
||||
|
||||
doc.set_status("Completed")
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
doc.log_error("Unable to repost item valuation")
|
||||
@ -142,9 +146,9 @@ def repost(doc):
|
||||
message += "<br>" + "Traceback: <br>" + traceback
|
||||
frappe.db.set_value(doc.doctype, doc.name, "error_log", message)
|
||||
|
||||
notify_error_to_stock_managers(doc, message)
|
||||
doc.set_status("Failed")
|
||||
raise
|
||||
if not isinstance(e, RecoverableErrors):
|
||||
notify_error_to_stock_managers(doc, message)
|
||||
doc.set_status("Failed")
|
||||
finally:
|
||||
if not frappe.flags.in_test:
|
||||
frappe.db.commit()
|
||||
|
@ -672,7 +672,8 @@ class StockEntry(StockController):
|
||||
batch_no=d.batch_no,
|
||||
)
|
||||
|
||||
d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
|
||||
# do not round off basic rate to avoid precision loss
|
||||
d.basic_rate = flt(d.basic_rate)
|
||||
if d.is_process_loss:
|
||||
d.basic_rate = flt(0.0)
|
||||
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
|
||||
@ -720,7 +721,7 @@ class StockEntry(StockController):
|
||||
total_fg_qty = sum([flt(d.transfer_qty) for d in self.items if d.is_finished_item])
|
||||
return flt(outgoing_items_cost / total_fg_qty)
|
||||
|
||||
def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0):
|
||||
def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0) -> float:
|
||||
scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
|
||||
|
||||
# Get raw materials cost from BOM if multiple material consumption entries
|
||||
@ -760,10 +761,8 @@ class StockEntry(StockController):
|
||||
for d in self.get("items"):
|
||||
if d.transfer_qty:
|
||||
d.amount = flt(flt(d.basic_amount) + flt(d.additional_cost), d.precision("amount"))
|
||||
d.valuation_rate = flt(
|
||||
flt(d.basic_rate) + (flt(d.additional_cost) / flt(d.transfer_qty)),
|
||||
d.precision("valuation_rate"),
|
||||
)
|
||||
# Do not round off valuation rate to avoid precision loss
|
||||
d.valuation_rate = flt(d.basic_rate) + (flt(d.additional_cost) / flt(d.transfer_qty))
|
||||
|
||||
def set_total_incoming_outgoing_value(self):
|
||||
self.total_incoming_value = self.total_outgoing_value = 0.0
|
||||
|
@ -8,9 +8,8 @@ import frappe
|
||||
from frappe.core.page.permission_manager.permission_manager import reset
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.query_builder.functions import CombineDatetime
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, today
|
||||
from frappe.utils.data import add_to_date
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, add_to_date, flt, today
|
||||
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import rename_gle_sle_docs
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
@ -1219,6 +1218,41 @@ class TestStockLedgerEntry(FrappeTestCase):
|
||||
except Exception as e:
|
||||
self.fail("Double processing of qty for clashing timestamp.")
|
||||
|
||||
@change_settings("System Settings", {"float_precision": 3, "currency_precision": 2})
|
||||
def test_transfer_invariants(self):
|
||||
"""Extact stock value should be transferred."""
|
||||
|
||||
item = make_item(
|
||||
properties={
|
||||
"valuation_method": "Moving Average",
|
||||
"stock_uom": "Kg",
|
||||
}
|
||||
).name
|
||||
source_warehouse = "Stores - TCP1"
|
||||
target_warehouse = "Finished Goods - TCP1"
|
||||
|
||||
make_purchase_receipt(
|
||||
item=item,
|
||||
warehouse=source_warehouse,
|
||||
qty=20,
|
||||
conversion_factor=1000,
|
||||
uom="Tonne",
|
||||
rate=156_526.0,
|
||||
company="_Test Company with perpetual inventory",
|
||||
)
|
||||
transfer = make_stock_entry(
|
||||
item=item, from_warehouse=source_warehouse, to_warehouse=target_warehouse, qty=1_728.0
|
||||
)
|
||||
|
||||
filters = {"voucher_no": transfer.name, "voucher_type": transfer.doctype, "is_cancelled": 0}
|
||||
sles = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=["*"],
|
||||
filters=filters,
|
||||
order_by="timestamp(posting_date, posting_time), creation",
|
||||
)
|
||||
self.assertEqual(abs(sles[0].stock_value_difference), sles[1].stock_value_difference)
|
||||
|
||||
|
||||
def create_repack_entry(**args):
|
||||
args = frappe._dict(args)
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user