Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion india_compliance/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "15.17.1"
__version__ = "15.18.0"
2 changes: 1 addition & 1 deletion india_compliance/gst_india/constants/e_waybill.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
buying_address = {
"bill_from": "supplier_address",
"bill_to": "billing_address",
"ship_from": "supplier_address",
"ship_from": "dispatch_address",
"ship_to": "shipping_address",
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,18 @@ frappe.ui.form.on("Bill of Entry", {
get_query() {
return {
query: "india_compliance.gst_india.doctype.bill_of_entry.bill_of_entry.fetch_pending_boe_invoices",
}
};
},
add_filters_group: 1,
action: function (selections, args) {
if (selections.length === 0) {
frappe.msgprint(
__("Please select at least one Purchase Invoice"),
__("No Selection")
);
return;
}

frm.call("get_items_from_purchase_invoice", {
purchase_invoices: selections,
}).then(d.dialog.hide());
Expand Down
45 changes: 26 additions & 19 deletions india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import today
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
Expand Down Expand Up @@ -306,8 +306,7 @@ def validate_item_tax_template(self):

def get_gl_entries(self):
# company_currency is required by get_gl_dict
# nosemgrep
self.company_currency = erpnext.get_company_currency(self.company)
self.company_currency = erpnext.get_company_currency(self.company) # nosemgrep

gl_entries = []
remarks = "No Remarks"
Expand Down Expand Up @@ -391,6 +390,10 @@ def get_asset_items(self):

@frappe.whitelist()
def get_items_from_purchase_invoice(self, purchase_invoices):
if not purchase_invoices:
frappe.msgprint(_("No Purchase Invoices selected"))
return

frappe.has_permission("Bill Of Entry", "write")
frappe.has_permission("Purchase Invoice", "read")

Expand Down Expand Up @@ -437,19 +440,18 @@ def update_pending_boe_qty(self):
submitted_boe_qty = (
frappe.qb.from_(boe_item)
.select(boe_item.pi_detail, Sum(boe_item.qty).as_("qty"))
.where(boe_item.pi_detail.isin(pi_item_names))
.where(boe_item.docstatus == 1)
.where((boe_item.pi_detail.isin(pi_item_names)) & (boe_item.docstatus == 1))
.groupby(boe_item.pi_detail)
)
).as_("submitted_boe_qty")

(
frappe.qb.update(pi_item)
.left_join(submitted_boe_qty)
.on(pi_item.name == submitted_boe_qty.pi_detail)
.set(
pi_item.pending_boe_qty,
pi_item.qty - submitted_boe_qty.qty,
pi_item.qty - IfNull(submitted_boe_qty.qty, 0),
)
.from_(submitted_boe_qty)
.where(pi_item.name == submitted_boe_qty.pi_detail)
.where(pi_item.name.isin(pi_item_names))
.run()
)
Expand Down Expand Up @@ -792,23 +794,28 @@ def get_pi_items(purchase_invoices):


@frappe.whitelist()
def fetch_pending_boe_invoices(*args, **kwargs):
def fetch_pending_boe_invoices(doctype, txt, searchfield, start, page_len, filters):
frappe.has_permission("Purchase Invoice", "read")

filters = next((arg for arg in args if isinstance(arg, dict)), {})
filters = frappe._dict(filters)

if txt and not filters.get("name"):
filters.name = ["like", f"%{txt}%"]

# TODO: fix required in frappe
if filters.name and filters.name[1] is None:
filters.name = ["!=", ""]

return frappe.get_all(
"Purchase Invoice",
filters={
**filters,
"docstatus": 1,
"company": filters.get("company"),
"company_gstin": filters.get("company_gstin"),
"gst_category": "Overseas",
"pending_boe_qty": [">", 0],
},
fields=[
"name",
"company",
"company_gstin",
],
fields=["name", "company", "company_gstin"],
limit_start=start,
limit_page_length=page_len,
distinct=True,
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,67 @@ def test_charge_type_actual_without_item_wise_tax_rates(self):
)

boe.save()


def test_pending_boe_qty(self):
pi = create_purchase_invoice(supplier="_Test Foreign Supplier", update_stock=1)

self.assertDocumentEqual(
{
"pending_boe_qty": 1,
},
pi.items[0],
)

# Create BOE
boe = make_bill_of_entry(pi.name)
boe.bill_of_entry_no = "123"
boe.bill_of_entry_date = today()
boe.save()
boe.submit()

pi.reload()
self.assertDocumentEqual(
{
"pending_boe_qty": 0,
},
pi.items[0],
)

boe.cancel()
pi.reload()

self.assertDocumentEqual(
{
"pending_boe_qty": 1,
},
pi.items[0],
)

# with partial boe
pi = create_purchase_invoice(
supplier="_Test Foreign Supplier", update_stock=1, qty=2
)
boe = make_bill_of_entry(pi.name)
boe.bill_of_entry_no = "123"
boe.bill_of_entry_date = today()
boe.items[0].qty = 1
boe.save()
boe.submit()

pi.reload()
self.assertDocumentEqual(
{
"pending_boe_qty": 1,
},
pi.items[0],
)

boe.cancel()
pi.reload()
self.assertDocumentEqual(
{
"pending_boe_qty": 2,
},
pi.items[0],
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import frappe
from frappe import _, unscrub
from frappe.utils import flt, sbool
from frappe.utils.data import getdate

from india_compliance.gst_india.api_classes.taxpayer_returns import GSTR1API
from india_compliance.gst_india.constants import STATUS_CODE_MAP
from india_compliance.gst_india.doctype.gstr_action.gstr_action import set_gstr_actions
from india_compliance.gst_india.utils.gstin_info import get_and_update_filing_preference
from india_compliance.gst_india.utils.gstr_1 import (
CATEGORY_SUB_CATEGORY_MAPPING,
PREVIOUS_VERSION,
QUARTERLY_KEYS,
SUBCATEGORIES_NOT_CONSIDERED_IN_TOTAL_TAX,
SUBCATEGORIES_NOT_CONSIDERED_IN_TOTAL_TAXABLE_VALUE,
Expand Down Expand Up @@ -42,7 +44,7 @@ class SummarizeGSTR1:
"total_cess_amount": 0,
}

def get_summarized_data(self, data, is_filed=False):
def get_summarized_data(self, data, filing_from, is_filed=False):
"""
Helper function to summarize data for each sub-category
"""
Expand All @@ -51,9 +53,9 @@ def get_summarized_data(self, data, is_filed=False):

subcategory_summary = self.get_subcategory_summary(data)

return self.get_overall_summary(subcategory_summary)
return self.get_overall_summary(subcategory_summary, filing_from)

def get_overall_summary(self, subcategory_summary):
def get_overall_summary(self, subcategory_summary, filing_from):
"""
Summarize data for each category with subcategories

Expand All @@ -63,7 +65,11 @@ def get_overall_summary(self, subcategory_summary):
3. Remove category row if no records
4. Round Values
"""
cateogory_summary = []
category_summary = []
hsn_bifurcation_from = frappe.db.get_single_value(
"GST Settings", "hsn_bifurcation_from"
)

for category, sub_categories in CATEGORY_SUB_CATEGORY_MAPPING.items():
# Init category row
category = category.value
Expand All @@ -74,9 +80,14 @@ def get_overall_summary(self, subcategory_summary):
**self.AMOUNT_FIELDS,
}

cateogory_summary.append(summary_row)
category_summary.append(summary_row)
remove_category_row = True

# Backwards compatibility
if (filing_from < hsn_bifurcation_from) and category in PREVIOUS_VERSION:
sub_categories = PREVIOUS_VERSION[category]


for subcategory in sub_categories:
# update category row
subcategory = subcategory.value
Expand All @@ -90,27 +101,27 @@ def get_overall_summary(self, subcategory_summary):
summary_row[key] += subcategory_row[key]

# add subcategory row
cateogory_summary.append(subcategory_row)
category_summary.append(subcategory_row)
remove_category_row = False

if not summary_row["no_of_records"]:
summary_row["no_of_records"] = ""

if remove_category_row:
cateogory_summary.remove(summary_row)
category_summary.remove(summary_row)

for key in QUARTERLY_KEYS:
if key not in subcategory_summary:
continue

cateogory_summary.append(subcategory_summary.get(key))
category_summary.append(subcategory_summary.get(key))

# Round Values
for row in cateogory_summary:
for row in category_summary:
for key, value in row.items():
if isinstance(value, (int, float)):
row[key] = flt(value, 2)
return cateogory_summary
return category_summary

def get_subcategory_summary(self, data):
"""
Expand Down Expand Up @@ -146,7 +157,11 @@ def get_subcategory_summary(self, data):
elif subcategory == GSTR1_SubCategory.DOC_ISSUE.value:
self.count_doc_issue_summary(summary_row, row)

elif subcategory == GSTR1_SubCategory.HSN.value:
elif subcategory in (
GSTR1_SubCategory.HSN_B2B.value,
GSTR1_SubCategory.HSN_B2C.value,
GSTR1_SubCategory.HSN.value, # Backwards compatibility
):
self.count_hsn_summary(summary_row)

for subcategory in subcategory_summary.keys():
Expand Down Expand Up @@ -637,7 +652,7 @@ def generate_gstr1_data(self, filters, callback=None):
data[gov_data_field] = self.normalize_data(gov_data)
data["books"] = self.normalize_data(books_data)

self.summarize_data(data)
self.summarize_data(data, filters)
return callback and callback(filters)

def set_filing_preference(self):
Expand All @@ -660,7 +675,8 @@ def generate_only_books_data(self, data, filters, callback=None):
data["books"] = self.normalize_data(books_data)
data["status"] = status

self.summarize_data(data)
self.summarize_data(data, filters)

return callback and callback(filters)

# GET DATA
Expand Down Expand Up @@ -718,7 +734,7 @@ def get_books_gstr1_data(self, filters, aggregate=False):
return books_data

# DATA MODIFIERS
def summarize_data(self, data):
def summarize_data(self, data, filters):
"""
Summarize data for all fields => reconcile, filed, unfiled, books

Expand Down Expand Up @@ -746,8 +762,9 @@ def summarize_data(self, data):
data[field] = _data
continue

filing_from = getdate(f"01-{filters.month_or_quarter}-{filters.year}")
summary_data = self.get_summarized_data(
data[key], self.filing_status == "Filed"
data[key], filing_from, self.filing_status == "Filed"
)

if key == "reconcile":
Expand Down Expand Up @@ -807,7 +824,6 @@ def get_net_liability_from_amendments(self):


class FileGSTR1:

def reset_gstr1(self, is_nil_return, force):
verify_request_in_progress(self, force)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"gstr_1_section_break",
"enable_gstr_1_api",
"compare_unfiled_data",
"hsn_bifurcation_from",
"column_break_cxmn",
"restrict_changes_after_gstr_1",
"role_allowed_to_modify",
Expand Down Expand Up @@ -688,6 +689,12 @@
"fieldname": "e_invoice_reporting_time_limit_days",
"fieldtype": "Int",
"label": "e-Invoice Reporting Time Limit (in Days)"
},
{
"description": "The HSN Summary Data will be bifurcated into B2B and B2C after this date.",
"fieldname": "hsn_bifurcation_from",
"fieldtype": "Date",
"label": "GSTR-1 HSN Bifurcation From"
}
],
"grid_page_length": 50,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const GSTR1_SubCategory = {
AT: "Advances Received",
TXP: "Advances Adjusted",
HSN: "HSN Summary",
HSN_B2B: "HSN Summary - B2B",
HSN_B2C: "HSN Summary - B2C",
DOC_ISSUE: "Document Issued",

SUPECOM_52: "Liable to collect tax u/s 52(TCS)",
Expand Down Expand Up @@ -1722,7 +1724,9 @@ class BooksTab extends GSTR1_TabManager {
[GSTR1_SubCategory.AT]: this.get_advances_received_columns,
[GSTR1_SubCategory.TXP]: this.get_advances_adjusted_columns,

[GSTR1_SubCategory.HSN]: this.get_hsn_columns,
[GSTR1_SubCategory.HSN]: this.get_hsn_columns,// Backwards compatibility
[GSTR1_SubCategory.HSN_B2B]: this.get_hsn_columns,
[GSTR1_SubCategory.HSN_B2C]: this.get_hsn_columns,

[GSTR1_SubCategory.DOC_ISSUE]: this.get_documents_issued_columns,
};
Expand Down
Loading