19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:21 +01:00
parent 7dc55599c6
commit 7f43bbbfcc
650 changed files with 45260 additions and 33436 deletions

View file

@ -21,38 +21,18 @@ pip install odoo-bringout-oca-ocb-l10n_in
## Dependencies
This addon depends on:
- account_tax_python
- base_vat
## Manifest Information
- **Name**: Indian - Accounting
- **Version**: 2.0
- **Category**: Accounting/Localizations/Account Charts
- **License**: LGPL-3
- **Installable**: False
- account_debit_note
- account
- iap
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `l10n_in`.
- Repository: https://github.com/OCA/OCB
- Branch: 19.0
- Path: addons/l10n_in
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md
This package preserves the original LGPL-3 license.

View file

@ -3,3 +3,12 @@
from . import models
from . import demo
from . import wizard
def init_settings(env):
# Activate cash rounding by default for all companies as soon as the module is installed.
group_user = env.ref('base.group_user').sudo()
group_user._apply_group(env.ref('account.group_cash_rounding'))
def post_init(env):
init_settings(env)

View file

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Indian - Accounting',
'website': 'https://www.odoo.com/documentation/latest/applications/finance/fiscal_localizations/india.html',
'icon': '/account/static/description/l10n.png',
'countries': ['in'],
'version': '2.0',
'description': """
Indian Accounting: Chart of Account.
@ -18,40 +19,61 @@ Sheet, now only Vertical format has been permitted Which is Supported By Odoo.
""",
'category': 'Accounting/Localizations/Account Charts',
'depends': [
'account_tax_python', 'base_vat',
'account_tax_python',
'base_vat',
'account_debit_note',
'account',
'iap',
],
'auto_install': ['account'],
'data': [
'security/l10n_in_security.xml',
'security/ir.model.access.csv',
'data/account_tax_group_data.xml',
"data/iap_service_data.xml",
'data/account.account.tag.csv',
'data/l10n_in_chart_data.xml',
'data/account.account.template.csv',
'data/l10n_in_chart_post_data.xml',
'data/account_tax_template_data.xml',
'data/account_fiscal_position_data.xml',
'data/l10n_in.port.code.csv',
'data/res_country_state_data.xml',
'data/res_country_group.xml',
'data/uom_data.xml',
'data/res_partner_industry.xml',
'data/account_cash_rounding.xml',
'data/account_tax_report_tcs_data.xml',
'data/account_tax_report_tds_data.xml',
'data/l10n_in.section.alert.csv',
'wizard/l10n_in_withhold_wizard.xml',
'views/l10n_in_pan_entity_views.xml',
'views/l10n_in_section_alert_views.xml',
'views/account_account_views.xml',
'views/account_invoice_views.xml',
'views/account_move_line_views.xml',
'views/account_payment_views.xml',
'views/account_journal_views.xml',
'views/res_config_settings_views.xml',
'views/product_template_view.xml',
'views/port_code_views.xml',
'views/res_company_views.xml',
'views/report_invoice.xml',
'views/res_country_state_view.xml',
'views/res_partner_views.xml',
'views/account_tax_views.xml',
'views/uom_uom_views.xml',
'views/report_template.xml',
'data/account_chart_template_data.xml',
'report/audit_trail_report_views.xml',
],
'demo': [
'demo/demo_company.xml',
'demo/res_partner_demo.xml',
'demo/product_demo.xml',
'demo/account_invoice_demo.xml',
'demo/demo_company.xml',
],
'post_init_hook': 'post_init',
'author': 'Odoo S.A.',
'license': 'LGPL-3',
'assets': {
'web.assets_backend': [
'l10n_in/static/src/components/**/*',
'l10n_in/static/src/helpers/*.js',
],
'web.assets_frontend': [
'l10n_in/static/src/components/tests_shared_js_python/*',
'l10n_in/static/src/helpers/*.js',
],
},
}

View file

@ -1,47 +1,16 @@
"id","name","applicability"
"tax_tag_base_sgst","BASE SGST","taxes"
"tax_tag_base_cgst","BASE CGST","taxes"
"tax_tag_base_igst","BASE IGST","taxes"
"tax_tag_non_itc_base_sgst","NON ITC BASE SGST","taxes"
"tax_tag_non_itc_base_cgst","NON ITC BASE CGST","taxes"
"tax_tag_non_itc_base_igst","NON ITC BASE IGST","taxes"
"tax_tag_other_non_itc_base_sgst","Other NON ITC BASE SGST","taxes"
"tax_tag_other_non_itc_base_cgst","Other NON ITC BASE CGST","taxes"
"tax_tag_other_non_itc_base_igst","Other NON ITC BASE IGST","taxes"
"tax_tag_base_cess","BASE CESS","taxes"
"tax_tag_base_state_cess","BASE STATE CESS","taxes"
"tax_tag_non_itc_base_cess","NON ITC BASE CESS","taxes"
"tax_tag_other_non_itc_base_cess","Other NON ITC BASE CESS","taxes"
"tax_tag_exempt","EXEMPT","taxes"
"tax_tag_nil_rated","NIL-RATED","taxes"
"tax_tag_zero_rated","ZERO-RATED","taxes"
"tax_tag_non_gst_supplies","NON GST SUPPLIES","taxes"
"tax_tag_base_sgst_rc","BASE SGST (RC)","taxes"
"tax_tag_base_cgst_rc","BASE CGST (RC)","taxes"
"tax_tag_base_igst_rc","BASE IGST (RC)","taxes"
"tax_tag_base_cess_rc","BASE CESS (RC)","taxes"
"tax_tag_sgst","SGST","taxes"
"tax_tag_cgst","CGST","taxes"
"tax_tag_igst","IGST","taxes"
"tax_tag_non_itc_sgst","NON ITC SGST","taxes"
"tax_tag_non_itc_cgst","NON ITC CGST","taxes"
"tax_tag_non_itc_igst","NON ITC IGST","taxes"
"tax_tag_other_non_itc_sgst","Other NON ITC SGST","taxes"
"tax_tag_other_non_itc_cgst","Other NON ITC CGST","taxes"
"tax_tag_other_non_itc_igst","Other NON ITC IGST","taxes"
"tax_tag_cess","CESS","taxes"
"tax_tag_state_cess","STATE CESS","taxes"
"tax_tag_non_itc_cess","NON ITC CESS","taxes"
"tax_tag_other_non_itc_cess","Other NON ITC CESS","taxes"
"tax_tag_sgst_rc","SGST (RC)","taxes"
"tax_tag_cgst_rc","CGST (RC)","taxes"
"tax_tag_igst_rc","IGST (RC)","taxes"
"tax_tag_non_itc_sgst_rc","NON ITC SGST (RC)","taxes"
"tax_tag_non_itc_cgst_rc","NON ITC CGST (RC)","taxes"
"tax_tag_non_itc_igst_rc","NON ITC IGST (RC)","taxes"
"tax_tag_other_non_itc_sgst_rc","Other NON ITC SGST (RC)","taxes"
"tax_tag_other_non_itc_cgst_rc","Other NON ITC CGST (RC)","taxes"
"tax_tag_other_non_itc_igst_rc","Other NON ITC IGST (RC)","taxes"
"tax_tag_cess_rc","CESS (RC)","taxes"
"tax_tag_non_itc_cess_rc","NON ITC CESS (RC)","taxes"
"tax_tag_other_non_itc_cess_rc","Other NON ITC CESS (RC)","taxes"
"id","name","applicability","country_id/id"
"tax_tag_base_sgst","BASE SGST","taxes","base.in"
"tax_tag_base_cgst","BASE CGST","taxes","base.in"
"tax_tag_base_igst","BASE IGST","taxes","base.in"
"tax_tag_base_cess","BASE CESS","taxes","base.in"
"tax_tag_base_state_cess","BASE STATE CESS","taxes","base.in"
"tax_tag_sgst","SGST","taxes","base.in"
"tax_tag_cgst","CGST","taxes","base.in"
"tax_tag_igst","IGST","taxes","base.in"
"tax_tag_cess","CESS","taxes","base.in"
"tax_tag_state_cess","STATE CESS","taxes","base.in"
"tax_tag_non_itc","NON ITC","taxes","base.in"
"tax_tag_other_non_itc","Other NON ITC","taxes","base.in"
"tax_tag_eco_9_5","ECO 9(5)","taxes","base.in"
"tax_tag_eco_tcs_52","ECO TCS 52","taxes","base.in"
"account_tag_closing_stock","Closing Stock","accounts","base.in"

1 id name applicability country_id/id
2 tax_tag_base_sgst BASE SGST taxes base.in
3 tax_tag_base_cgst BASE CGST taxes base.in
4 tax_tag_base_igst BASE IGST taxes base.in
5 tax_tag_non_itc_base_sgst tax_tag_base_cess NON ITC BASE SGST BASE CESS taxes base.in
6 tax_tag_non_itc_base_cgst tax_tag_base_state_cess NON ITC BASE CGST BASE STATE CESS taxes base.in
7 tax_tag_non_itc_base_igst tax_tag_sgst NON ITC BASE IGST SGST taxes base.in
8 tax_tag_other_non_itc_base_sgst tax_tag_cgst Other NON ITC BASE SGST CGST taxes base.in
9 tax_tag_other_non_itc_base_cgst tax_tag_igst Other NON ITC BASE CGST IGST taxes base.in
10 tax_tag_other_non_itc_base_igst tax_tag_cess Other NON ITC BASE IGST CESS taxes base.in
11 tax_tag_base_cess tax_tag_state_cess BASE CESS STATE CESS taxes base.in
12 tax_tag_base_state_cess tax_tag_non_itc BASE STATE CESS NON ITC taxes base.in
13 tax_tag_non_itc_base_cess tax_tag_other_non_itc NON ITC BASE CESS Other NON ITC taxes base.in
14 tax_tag_other_non_itc_base_cess tax_tag_eco_9_5 Other NON ITC BASE CESS ECO 9(5) taxes base.in
15 tax_tag_exempt tax_tag_eco_tcs_52 EXEMPT ECO TCS 52 taxes base.in
16 tax_tag_nil_rated account_tag_closing_stock NIL-RATED Closing Stock taxes accounts base.in
tax_tag_zero_rated ZERO-RATED taxes
tax_tag_non_gst_supplies NON GST SUPPLIES taxes
tax_tag_base_sgst_rc BASE SGST (RC) taxes
tax_tag_base_cgst_rc BASE CGST (RC) taxes
tax_tag_base_igst_rc BASE IGST (RC) taxes
tax_tag_base_cess_rc BASE CESS (RC) taxes
tax_tag_sgst SGST taxes
tax_tag_cgst CGST taxes
tax_tag_igst IGST taxes
tax_tag_non_itc_sgst NON ITC SGST taxes
tax_tag_non_itc_cgst NON ITC CGST taxes
tax_tag_non_itc_igst NON ITC IGST taxes
tax_tag_other_non_itc_sgst Other NON ITC SGST taxes
tax_tag_other_non_itc_cgst Other NON ITC CGST taxes
tax_tag_other_non_itc_igst Other NON ITC IGST taxes
tax_tag_cess CESS taxes
tax_tag_state_cess STATE CESS taxes
tax_tag_non_itc_cess NON ITC CESS taxes
tax_tag_other_non_itc_cess Other NON ITC CESS taxes
tax_tag_sgst_rc SGST (RC) taxes
tax_tag_cgst_rc CGST (RC) taxes
tax_tag_igst_rc IGST (RC) taxes
tax_tag_non_itc_sgst_rc NON ITC SGST (RC) taxes
tax_tag_non_itc_cgst_rc NON ITC CGST (RC) taxes
tax_tag_non_itc_igst_rc NON ITC IGST (RC) taxes
tax_tag_other_non_itc_sgst_rc Other NON ITC SGST (RC) taxes
tax_tag_other_non_itc_cgst_rc Other NON ITC CGST (RC) taxes
tax_tag_other_non_itc_igst_rc Other NON ITC IGST (RC) taxes
tax_tag_cess_rc CESS (RC) taxes
tax_tag_non_itc_cess_rc NON ITC CESS (RC) taxes
tax_tag_other_non_itc_cess_rc Other NON ITC CESS (RC) taxes

View file

@ -1,69 +0,0 @@
"id","name","code","account_type","chart_template_id/id","tag_ids/id","reconcile"
"p10031","Inventories","10031","asset_current","l10n_in.indian_chart_template_standard","","False"
"p10040","Debtors","10040","asset_receivable","l10n_in.indian_chart_template_standard","","True"
"p10041","Debtors (PoS)","10041","asset_receivable","l10n_in.indian_chart_template_standard","","True"
"p10051","SGST Receivable","10051","asset_current","l10n_in.indian_chart_template_standard","l10n_in.sgst_tag_account","False"
"p10052","CGST Receivable","10052","asset_current","l10n_in.indian_chart_template_standard","l10n_in.cgst_tag_account","False"
"p10053","IGST Receivable","10053","asset_current","l10n_in.indian_chart_template_standard","l10n_in.igst_tag_account","False"
"p10057","Reverse Charge Tax Receivable","10057","asset_current","l10n_in.indian_chart_template_standard","","False"
"p10054","TDS Receivable","10058","asset_current","l10n_in.indian_chart_template_standard","","False"
"p10059","Tax Current Account - Receivable","10059","asset_current","l10n_in.indian_chart_template_standard","","False"
"p10061","Deposit Account","10061","asset_current","l10n_in.indian_chart_template_standard","","False"
"p10071","Prepaid Insurance","10071","asset_current","l10n_in.indian_chart_template_standard","","False"
"p1011","Buildings","1011","asset_fixed","l10n_in.indian_chart_template_standard","","False"
"p1012","Land","1012","asset_fixed","l10n_in.indian_chart_template_standard","","False"
"p1013","Equipments","1013","asset_fixed","l10n_in.indian_chart_template_standard","","False"
"p1014","Vehicle","1014","asset_fixed","l10n_in.indian_chart_template_standard","","False"
"p1015","Computer/Laptops (Assets)","1015","asset_fixed","l10n_in.indian_chart_template_standard","","False"
"p1016","Furniture","1016","asset_fixed","l10n_in.indian_chart_template_standard","","False"
"p1017","Air Conditionar","1017","asset_fixed","l10n_in.indian_chart_template_standard","","False"
"p1018","Misc Assets","1018","asset_fixed","l10n_in.indian_chart_template_standard","","False"
"p1111","Capital Account","1111","liability_current","l10n_in.indian_chart_template_standard","","False"
"p1112","Reserve And Surplus Account","1112","liability_current","l10n_in.indian_chart_template_standard","","False"
"p11211","Creditors","11211","liability_payable","l10n_in.indian_chart_template_standard","","True"
"p11221","Bank OD Account","11221","liability_current","l10n_in.indian_chart_template_standard","","False"
"p11222","Secured Loan Account","11222","liability_current","l10n_in.indian_chart_template_standard","","False"
"p11223","Unsecured Loan Account","11223","liability_current","l10n_in.indian_chart_template_standard","","False"
"p11231","TDS Payable","11231","liability_current","l10n_in.indian_chart_template_standard","","False"
"p11232","SGST Payable","11232","liability_current","l10n_in.indian_chart_template_standard","l10n_in.sgst_tag_account","False"
"p11233","CGST Payable","11233","liability_current","l10n_in.indian_chart_template_standard","l10n_in.cgst_tag_account","False"
"p11234","IGST Payable","11234","liability_current","l10n_in.indian_chart_template_standard","l10n_in.igst_tag_account","False"
"p11239","Tax Current Account - Payable","11239","liability_current","l10n_in.indian_chart_template_standard","","False"
"p11241","Wages Payable","11241","liability_current","l10n_in.indian_chart_template_standard","","False"
"p11242","Interest Payable","11242","liability_current","l10n_in.indian_chart_template_standard","","False"
"p11243","Notes Payable","11243","liability_current","l10n_in.indian_chart_template_standard","","False"
"p20011","Local Sales","20011","income","l10n_in.indian_chart_template_standard","","False"
"p20012","Retail Sales","20012","income","l10n_in.indian_chart_template_standard","","False"
"p20013","Export Sales","20013","income","l10n_in.indian_chart_template_standard","","False"
"p20021","Local Services","20021","income","l10n_in.indian_chart_template_standard","","False"
"p20022","Export Services","20022","income","l10n_in.indian_chart_template_standard","","False"
"p2010","Interest Revenues","2010","income","l10n_in.indian_chart_template_standard","","False"
"p2011","Gain on Sale of Assets","2011","income","l10n_in.indian_chart_template_standard","","False"
"2012","Write off Income","2012","income","l10n_in.indian_chart_template_standard","","False"
"p2013","Foreign Exchange Profit","2013","income_other","l10n_in.indian_chart_template_standard","","False"
"p2100","Electricity Expense","2100","expense","l10n_in.indian_chart_template_standard","","False"
"p2101","Salary Expense","2101","expense","l10n_in.indian_chart_template_standard","","False"
"p2102","Office Rent","2102","expense","l10n_in.indian_chart_template_standard","","False"
"p2103","House Keeping Expense","2103","expense","l10n_in.indian_chart_template_standard","","False"
"p2104","Postage And Courier Expense","2104","expense","l10n_in.indian_chart_template_standard","","False"
"p2105","Internet Expense","2105","expense","l10n_in.indian_chart_template_standard","","False"
"p2106","Telephone Expense","2106","expense","l10n_in.indian_chart_template_standard","","False"
"p2107","Purchase Expense","2107","expense","l10n_in.indian_chart_template_standard","","False"
"p2108","Computer/Laptop Accessories","2108","expense","l10n_in.indian_chart_template_standard","","False"
"p2109","News Paper And Magazine","2109","expense","l10n_in.indian_chart_template_standard","","False"
"p2110","Business Promotion","2110","expense","l10n_in.indian_chart_template_standard","","False"
"p2111","Entertainment Expense","2111","expense","l10n_in.indian_chart_template_standard","","False"
"p2112","Professional Services","2112","expense","l10n_in.indian_chart_template_standard","","False"
"p2113","Bank Charges","2113","asset_cash","l10n_in.indian_chart_template_standard","","False"
"p2114","Diwali Bonus/Gift","2114","expense","l10n_in.indian_chart_template_standard","","False"
"p2115","Parts Purchase","2115","expense","l10n_in.indian_chart_template_standard","","False"
"p2116","Repairing Expense","2116","expense","l10n_in.indian_chart_template_standard","","False"
"p2117","Foreign Exchange Loss","2117","expense","l10n_in.indian_chart_template_standard","","False"
"p21181","Sales Commission Expense","21181","expense","l10n_in.indian_chart_template_standard","","False"
"p21182","Stationary Expense","21182","expense","l10n_in.indian_chart_template_standard","","False"
"p21183","Travelling Expense","21183","expense","l10n_in.indian_chart_template_standard","","False"
"p2121","Opening Stock","2121","expense","l10n_in.indian_chart_template_standard","","False"
"p2122","Purchase Stock","2122","expense","l10n_in.indian_chart_template_standard","","False"
"p2123","Closing Stock","2123","expense","l10n_in.indian_chart_template_standard","","False"
"p2131","Loss on Sale of Assets","2131","expense","l10n_in.indian_chart_template_standard","","False"
"p2132","Write Off Expense","2132","expense","l10n_in.indian_chart_template_standard","","False"
1 id name code account_type chart_template_id/id tag_ids/id reconcile
2 p10031 Inventories 10031 asset_current l10n_in.indian_chart_template_standard False
3 p10040 Debtors 10040 asset_receivable l10n_in.indian_chart_template_standard True
4 p10041 Debtors (PoS) 10041 asset_receivable l10n_in.indian_chart_template_standard True
5 p10051 SGST Receivable 10051 asset_current l10n_in.indian_chart_template_standard l10n_in.sgst_tag_account False
6 p10052 CGST Receivable 10052 asset_current l10n_in.indian_chart_template_standard l10n_in.cgst_tag_account False
7 p10053 IGST Receivable 10053 asset_current l10n_in.indian_chart_template_standard l10n_in.igst_tag_account False
8 p10057 Reverse Charge Tax Receivable 10057 asset_current l10n_in.indian_chart_template_standard False
9 p10054 TDS Receivable 10058 asset_current l10n_in.indian_chart_template_standard False
10 p10059 Tax Current Account - Receivable 10059 asset_current l10n_in.indian_chart_template_standard False
11 p10061 Deposit Account 10061 asset_current l10n_in.indian_chart_template_standard False
12 p10071 Prepaid Insurance 10071 asset_current l10n_in.indian_chart_template_standard False
13 p1011 Buildings 1011 asset_fixed l10n_in.indian_chart_template_standard False
14 p1012 Land 1012 asset_fixed l10n_in.indian_chart_template_standard False
15 p1013 Equipments 1013 asset_fixed l10n_in.indian_chart_template_standard False
16 p1014 Vehicle 1014 asset_fixed l10n_in.indian_chart_template_standard False
17 p1015 Computer/Laptops (Assets) 1015 asset_fixed l10n_in.indian_chart_template_standard False
18 p1016 Furniture 1016 asset_fixed l10n_in.indian_chart_template_standard False
19 p1017 Air Conditionar 1017 asset_fixed l10n_in.indian_chart_template_standard False
20 p1018 Misc Assets 1018 asset_fixed l10n_in.indian_chart_template_standard False
21 p1111 Capital Account 1111 liability_current l10n_in.indian_chart_template_standard False
22 p1112 Reserve And Surplus Account 1112 liability_current l10n_in.indian_chart_template_standard False
23 p11211 Creditors 11211 liability_payable l10n_in.indian_chart_template_standard True
24 p11221 Bank OD Account 11221 liability_current l10n_in.indian_chart_template_standard False
25 p11222 Secured Loan Account 11222 liability_current l10n_in.indian_chart_template_standard False
26 p11223 Unsecured Loan Account 11223 liability_current l10n_in.indian_chart_template_standard False
27 p11231 TDS Payable 11231 liability_current l10n_in.indian_chart_template_standard False
28 p11232 SGST Payable 11232 liability_current l10n_in.indian_chart_template_standard l10n_in.sgst_tag_account False
29 p11233 CGST Payable 11233 liability_current l10n_in.indian_chart_template_standard l10n_in.cgst_tag_account False
30 p11234 IGST Payable 11234 liability_current l10n_in.indian_chart_template_standard l10n_in.igst_tag_account False
31 p11239 Tax Current Account - Payable 11239 liability_current l10n_in.indian_chart_template_standard False
32 p11241 Wages Payable 11241 liability_current l10n_in.indian_chart_template_standard False
33 p11242 Interest Payable 11242 liability_current l10n_in.indian_chart_template_standard False
34 p11243 Notes Payable 11243 liability_current l10n_in.indian_chart_template_standard False
35 p20011 Local Sales 20011 income l10n_in.indian_chart_template_standard False
36 p20012 Retail Sales 20012 income l10n_in.indian_chart_template_standard False
37 p20013 Export Sales 20013 income l10n_in.indian_chart_template_standard False
38 p20021 Local Services 20021 income l10n_in.indian_chart_template_standard False
39 p20022 Export Services 20022 income l10n_in.indian_chart_template_standard False
40 p2010 Interest Revenues 2010 income l10n_in.indian_chart_template_standard False
41 p2011 Gain on Sale of Assets 2011 income l10n_in.indian_chart_template_standard False
42 2012 Write off Income 2012 income l10n_in.indian_chart_template_standard False
43 p2013 Foreign Exchange Profit 2013 income_other l10n_in.indian_chart_template_standard False
44 p2100 Electricity Expense 2100 expense l10n_in.indian_chart_template_standard False
45 p2101 Salary Expense 2101 expense l10n_in.indian_chart_template_standard False
46 p2102 Office Rent 2102 expense l10n_in.indian_chart_template_standard False
47 p2103 House Keeping Expense 2103 expense l10n_in.indian_chart_template_standard False
48 p2104 Postage And Courier Expense 2104 expense l10n_in.indian_chart_template_standard False
49 p2105 Internet Expense 2105 expense l10n_in.indian_chart_template_standard False
50 p2106 Telephone Expense 2106 expense l10n_in.indian_chart_template_standard False
51 p2107 Purchase Expense 2107 expense l10n_in.indian_chart_template_standard False
52 p2108 Computer/Laptop Accessories 2108 expense l10n_in.indian_chart_template_standard False
53 p2109 News Paper And Magazine 2109 expense l10n_in.indian_chart_template_standard False
54 p2110 Business Promotion 2110 expense l10n_in.indian_chart_template_standard False
55 p2111 Entertainment Expense 2111 expense l10n_in.indian_chart_template_standard False
56 p2112 Professional Services 2112 expense l10n_in.indian_chart_template_standard False
57 p2113 Bank Charges 2113 asset_cash l10n_in.indian_chart_template_standard False
58 p2114 Diwali Bonus/Gift 2114 expense l10n_in.indian_chart_template_standard False
59 p2115 Parts Purchase 2115 expense l10n_in.indian_chart_template_standard False
60 p2116 Repairing Expense 2116 expense l10n_in.indian_chart_template_standard False
61 p2117 Foreign Exchange Loss 2117 expense l10n_in.indian_chart_template_standard False
62 p21181 Sales Commission Expense 21181 expense l10n_in.indian_chart_template_standard False
63 p21182 Stationary Expense 21182 expense l10n_in.indian_chart_template_standard False
64 p21183 Travelling Expense 21183 expense l10n_in.indian_chart_template_standard False
65 p2121 Opening Stock 2121 expense l10n_in.indian_chart_template_standard False
66 p2122 Purchase Stock 2122 expense l10n_in.indian_chart_template_standard False
67 p2123 Closing Stock 2123 expense l10n_in.indian_chart_template_standard False
68 p2131 Loss on Sale of Assets 2131 expense l10n_in.indian_chart_template_standard False
69 p2132 Write Off Expense 2132 expense l10n_in.indian_chart_template_standard False

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="cash_rounding_in_half_up" model="account.cash.rounding">
<field name="name">Half Up</field>
<field name="rounding">1</field>
</record>
</odoo>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<function model="account.chart.template" name="try_loading">
<value eval="[ref('l10n_in.indian_chart_template_standard')]"/>
</function>
</data>
</odoo>

View file

@ -1,211 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Fiscal Position Templates -->
<record model="account.fiscal.position.template" id="fiscal_position_in_inter_state">
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="name">Inter State</field>
</record>
<record model="account.fiscal.position.template" id="fiscal_position_in_export">
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="name">Export</field>
</record>
<!-- Fiscal Position Tax Templates -->
<record id="account_fiscal_position_tax_in_sale_1_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_sale_1"/>
<field name="tax_dest_id" ref="igst_sale_1"/>
</record>
<record id="account_fiscal_position_tax_in_sale_2_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_sale_2"/>
<field name="tax_dest_id" ref="igst_sale_2"/>
</record>
<record id="account_fiscal_position_tax_in_sale_5_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_sale_5"/>
<field name="tax_dest_id" ref="igst_sale_5"/>
</record>
<record id="account_fiscal_position_tax_in_sale_12_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_sale_12"/>
<field name="tax_dest_id" ref="igst_sale_12"/>
</record>
<record id="account_fiscal_position_tax_in_sale_18_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_sale_18"/>
<field name="tax_dest_id" ref="igst_sale_18"/>
</record>
<record id="account_fiscal_position_tax_in_sale_28_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_sale_28"/>
<field name="tax_dest_id" ref="igst_sale_28"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_1_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_purchase_1"/>
<field name="tax_dest_id" ref="igst_purchase_1"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_2_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_purchase_2"/>
<field name="tax_dest_id" ref="igst_purchase_2"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_5_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_purchase_5"/>
<field name="tax_dest_id" ref="igst_purchase_5"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_12_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_purchase_12"/>
<field name="tax_dest_id" ref="igst_purchase_12"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_18_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_purchase_18"/>
<field name="tax_dest_id" ref="igst_purchase_18"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_28_inter" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_inter_state"/>
<field name="tax_src_id" ref="sgst_purchase_28"/>
<field name="tax_dest_id" ref="igst_purchase_28"/>
</record>
<record id="account_fiscal_position_tax_in_sale_1_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_sale_1"/>
<field name="tax_dest_id" ref="igst_sale_1"/>
</record>
<record id="account_fiscal_position_tax_in_sale_2_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_sale_2"/>
<field name="tax_dest_id" ref="igst_sale_2"/>
</record>
<record id="account_fiscal_position_tax_in_sale_5_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_sale_5"/>
<field name="tax_dest_id" ref="igst_sale_5"/>
</record>
<record id="account_fiscal_position_tax_in_sale_12_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_sale_12"/>
<field name="tax_dest_id" ref="igst_sale_12"/>
</record>
<record id="account_fiscal_position_tax_in_sale_18_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_sale_18"/>
<field name="tax_dest_id" ref="igst_sale_18"/>
</record>
<record id="account_fiscal_position_tax_in_sale_28_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_sale_28"/>
<field name="tax_dest_id" ref="igst_sale_28"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_1_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_purchase_1"/>
<field name="tax_dest_id" ref="igst_purchase_1"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_2_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_purchase_2"/>
<field name="tax_dest_id" ref="igst_purchase_2"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_5_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_purchase_5"/>
<field name="tax_dest_id" ref="igst_purchase_5"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_12_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_purchase_12"/>
<field name="tax_dest_id" ref="igst_purchase_12"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_18_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_purchase_18"/>
<field name="tax_dest_id" ref="igst_purchase_18"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_28_exp" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_export"/>
<field name="tax_src_id" ref="sgst_purchase_28"/>
<field name="tax_dest_id" ref="igst_purchase_28"/>
</record>
<record model="account.fiscal.position.template" id="fiscal_position_in_reverse_charge_intra">
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="name">Reverse charge Intra State</field>
</record>
<record id="account_fiscal_position_tax_in_purchase_1_intra_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_intra"/>
<field name="tax_src_id" ref="sgst_purchase_1"/>
<field name="tax_dest_id" ref="sgst_purchase_1_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_2_intra_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_intra"/>
<field name="tax_src_id" ref="sgst_purchase_2"/>
<field name="tax_dest_id" ref="sgst_purchase_2_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_5_intra_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_intra"/>
<field name="tax_src_id" ref="sgst_purchase_5"/>
<field name="tax_dest_id" ref="sgst_purchase_5_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_12_intra_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_intra"/>
<field name="tax_src_id" ref="sgst_purchase_12"/>
<field name="tax_dest_id" ref="sgst_purchase_12_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_18_intra_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_intra"/>
<field name="tax_src_id" ref="sgst_purchase_18"/>
<field name="tax_dest_id" ref="sgst_purchase_18_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_28_intra_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_intra"/>
<field name="tax_src_id" ref="sgst_purchase_28"/>
<field name="tax_dest_id" ref="sgst_purchase_28_rc"/>
</record>
<record model="account.fiscal.position.template" id="fiscal_position_in_reverse_charge_inter">
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="name">Reverse charge Inter State</field>
</record>
<record id="account_fiscal_position_tax_in_purchase_1_rc_inter_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_inter"/>
<field name="tax_src_id" ref="sgst_purchase_1"/>
<field name="tax_dest_id" ref="igst_purchase_1_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_2_rc_inter_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_inter"/>
<field name="tax_src_id" ref="sgst_purchase_2"/>
<field name="tax_dest_id" ref="igst_purchase_2_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_5_rc_inter_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_inter"/>
<field name="tax_src_id" ref="sgst_purchase_5"/>
<field name="tax_dest_id" ref="igst_purchase_5_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_12_rc_inter_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_inter"/>
<field name="tax_src_id" ref="sgst_purchase_12"/>
<field name="tax_dest_id" ref="igst_purchase_12_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_18_rc_inter_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_inter"/>
<field name="tax_src_id" ref="sgst_purchase_18"/>
<field name="tax_dest_id" ref="igst_purchase_18_rc"/>
</record>
<record id="account_fiscal_position_tax_in_purchase_28_rc_inter_rc" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_in_reverse_charge_inter"/>
<field name="tax_src_id" ref="sgst_purchase_28"/>
<field name="tax_dest_id" ref="igst_purchase_28_rc"/>
</record>
</odoo>

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="sgst_group" model="account.tax.group">
<field name="name">SGST</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="cgst_group" model="account.tax.group">
<field name="name">CGST</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="igst_group" model="account.tax.group">
<field name="name">IGST</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="cess_group" model="account.tax.group">
<field name="name">CESS</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="gst_group" model="account.tax.group">
<field name="name">GST</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="exempt_group" model="account.tax.group">
<field name="name">Exempt</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="nil_rated_group" model="account.tax.group">
<field name="name">Nil Rated</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="non_gst_supplies_group" model="account.tax.group">
<field name="name">Non GST Supplies</field>
<field name="country_id" ref="base.in"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo auto_sequence="1">
<record id="tcs_report" model="account.report">
<field name="name">TCS Report</field>
<field name="root_report_id" ref="account.generic_tax_report"/>
<field name="country_id" ref="base.in"/>
<field name="allow_foreign_vat" eval="True"/>
<field name="availability_condition">country</field>
<field name="column_ids">
<record id="tcs_report_balance" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
<field name="line_ids">
<record id="tcs_report_line_section_206c_1_alfhc" model="account.report.line">
<field name="name">Section 206C(1): Alcoholic Liquor for human consumption</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1_alfhc_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1) Alcoholic Liquor</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1_tl" model="account.report.line">
<field name="name">Section 206C(1): Tendu leaves</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1_tl_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1) Tendu leaves</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1_touafl" model="account.report.line">
<field name="name">Section 206C(1): Timber obtained under a forest lease</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1_touafl_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1) Timber (forest lease)</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1_tobaotuafl" model="account.report.line">
<field name="name">Section 206C(1): Timber obtained by any mode other than under a forest lease</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1_tobaotuafl_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1) Timber (other than under a forest lease)</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1_aofpnbtotl" model="account.report.line">
<field name="name">Section 206C(1): Any other forest produce not being timber or tendu leaves</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1_aofpnbtotl_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1) other forest produce</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1_s" model="account.report.line">
<field name="name">Section 206C(1): Scrap</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1_s_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1) Scrap</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1_mbcoloio" model="account.report.line">
<field name="name">Section 206C(1): Minrals, being coal or lignite or iron ore</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1_mbcoloio_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1) Minrals</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1c_pl" model="account.report.line">
<field name="name">Section 206C(1C): Parking lot</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1c_pl_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1C) Parking lot</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1c_tp" model="account.report.line">
<field name="name">Section 206C(1C): Toll plaza</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1c_tp_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1C) Toll plaza</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1c_maq" model="account.report.line">
<field name="name">Section 206C(1C): Mining and quarrying</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1c_maq_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1C) Mining and quarrying</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1f_mv" model="account.report.line">
<field name="name">Section 206C(1F): Motor Vehicle</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1f_mv_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1F)</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1g_som" model="account.report.line">
<field name="name">Section 206C(1G): Sum of money (above 7 lakhs) for remittance out of India</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1g_som_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1G) remittance out of India</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1g_soaotpp" model="account.report.line">
<field name="name">Section 206C(1G): Seller of an overseas tour program package</field>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1g_soaotpp_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-206C(1G) overseas tour program</field>
</record>
</field>
</record>
<record id="tcs_report_line_section_206c_1h_sog" model="account.report.line">
<field name="name">Section 206C(1H): Sale of Goods</field>
<field name="hide_if_zero" eval="1"/>
<field name="expression_ids">
<record id="tcs_report_line_section_206c_1h_sog_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">206C(1H)</field>
</record>
</field>
</record>
</field>
</record>
</odoo>

View file

@ -0,0 +1,328 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo auto_sequence="1">
<record id="tds_report" model="account.report">
<field name="name">TDS Report</field>
<field name="root_report_id" ref="account.generic_tax_report"/>
<field name="country_id" ref="base.in"/>
<field name="allow_foreign_vat" eval="True"/>
<field name="availability_condition">country</field>
<field name="column_ids">
<record id="tds_report_balance" model="account.report.column">
<field name="name">Balance</field>
<field name="expression_label">balance</field>
</record>
</field>
<field name="line_ids">
<record id="tds_report_line_section_192" model="account.report.line">
<field name="name">Section 192: Payment of salary</field>
<field name="expression_ids">
<record id="tds_report_line_section_192_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-192</field>
</record>
</field>
</record>
<record id="tds_report_line_section_192a" model="account.report.line">
<field name="name">Section 192A: Payment of accumulated balance of provident fund which is taxable in the hands of an employee</field>
<field name="expression_ids">
<record id="tds_report_line_section_192a_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-192A</field>
</record>
</field>
</record>
<record id="tds_report_line_section_193" model="account.report.line">
<field name="name">Section 193: Interest on securities</field>
<field name="expression_ids">
<record id="tds_report_line_section_193_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-193</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194" model="account.report.line">
<field name="name">Section 194: Income by way of dividend</field>
<field name="expression_ids">
<record id="tds_report_line_section_194_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194a" model="account.report.line">
<field name="name">Section 194A: Income by way of interest other than &quot;Interest on securities&quot;</field>
<field name="expression_ids">
<record id="tds_report_line_section_194a_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194A</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194b" model="account.report.line">
<field name="name">Section 194B: Income by way of winnings from lotteries, crossword puzzles, card games and other games of any sort</field>
<field name="expression_ids">
<record id="tds_report_line_section_194b_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194B</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194bb" model="account.report.line">
<field name="name">Section 194BB: Income by way of winnings from horse races</field>
<field name="expression_ids">
<record id="tds_report_line_section_194bb_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194BB</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194c" model="account.report.line">
<field name="name">Section 194C: Payment to contractor/sub-contractor</field>
<field name="expression_ids">
<record id="tds_report_line_section_194c_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194C</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194d" model="account.report.line">
<field name="name">Section 194D: Insurance commission</field>
<field name="expression_ids">
<record id="tds_report_line_section_194d_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194D</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194da" model="account.report.line">
<field name="name">Section 194DA: Payment in respect of life insurance policy</field>
<field name="expression_ids">
<record id="tds_report_line_section_194da_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194DA</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194e" model="account.report.line">
<field name="name">Section 194E: Payment to non-resident sportsmen/sports association</field>
<field name="expression_ids">
<record id="tds_report_line_section_194e_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194E</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194ee" model="account.report.line">
<field name="name">Section 194EE: Payment in respect of deposit under National Savings scheme</field>
<field name="expression_ids">
<record id="tds_report_line_section_194ee_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194EE</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194f" model="account.report.line">
<field name="name">Section 194F: Payment on account of repurchase of unit by Mutual Fund or Unit Trust of India</field>
<field name="expression_ids">
<record id="tds_report_line_section_194f_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194F</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194g" model="account.report.line">
<field name="name">Section 194G: Commission, etc., on sale of lottery tickets</field>
<field name="expression_ids">
<record id="tds_report_line_section_194g_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194G</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194h" model="account.report.line">
<field name="name">Section 194H: Commission or brokerage</field>
<field name="expression_ids">
<record id="tds_report_line_section_194h_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194H</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194i" model="account.report.line">
<field name="name">Section 194-I: Rent</field>
<field name="expression_ids">
<record id="tds_report_line_section_194i_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194I</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194ia" model="account.report.line">
<field name="name">Section 194-IA: Payment on transfer of certain immovable property other than agricultural land</field>
<field name="expression_ids">
<record id="tds_report_line_section_194ia_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194IA</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194ib" model="account.report.line">
<field name="name">Section 194-IB: Payment of rent by individual or HUF not liable to tax audit</field>
<field name="expression_ids">
<record id="tds_report_line_section_194ib_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194IB</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194ic" model="account.report.line">
<field name="name">Section 194-IC: Payment of monetary consideration under Joint Development Agreements</field>
<field name="expression_ids">
<record id="tds_report_line_section_194ic_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194IC</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194j" model="account.report.line">
<field name="name">Section 194J: Fees for professional or technical services</field>
<field name="expression_ids">
<record id="tds_report_line_section_194j_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194J</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194k" model="account.report.line">
<field name="name">Section 194K: Income in respect of units payable to resident person</field>
<field name="expression_ids">
<record id="tds_report_line_section_194k_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194K</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194la" model="account.report.line">
<field name="name">Section 194LA: Payment of compensation on acquisition of certain immovable property</field>
<field name="expression_ids">
<record id="tds_report_line_section_194la_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194LA</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194lba" model="account.report.line">
<field name="name">Section 194LBA(1): Business trust shall deduct tax while distributing, any interest received or receivable by it from a SPV or any income received from renting or leasing or letting out any real estate asset owned directly by it, to its unit holders.</field>
<field name="expression_ids">
<record id="tds_report_line_section_194lba_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194LBA(1)</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194lb" model="account.report.line">
<field name="name">Section 194LB: Payment of interest on infrastructure debt fund</field>
<field name="expression_ids">
<record id="tds_report_line_section_194lb_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194LB</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194lbb" model="account.report.line">
<field name="name">Section 194LBB: Investment fund paying an income to a unit holder [other than income which is exempt under Section 10(23FBB)]</field>
<field name="expression_ids">
<record id="tds_report_line_section_194lbb_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194LBB</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194lbc" model="account.report.line">
<field name="name">Section 194LBC: Income in respect of investment made in a securitisation trust (specified in Explanation of section115TCA)</field>
<field name="expression_ids">
<record id="tds_report_line_section_194lbc_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194LBC</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194m" model="account.report.line">
<field name="name">Section 194M: Payment of commission (not being insurance commission), brokerage, contractual fee, professional fee to a resident person by an Individual or a HUF who are not liable to deduct TDS under section 194C, 194H, or 194J.</field>
<field name="expression_ids">
<record id="tds_report_line_section_194m_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194M</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194n" model="account.report.line">
<field name="name">Section 194N: Cash withdrawal during the previous year from one or more account maintained by a person with a banking company, co-operative society engaged in business of banking or a post office</field>
<field name="expression_ids">
<record id="tds_report_line_section_194n_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194N</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194o" model="account.report.line">
<field name="name">Section 194-O: Payment or credit of amount by the e-commerce operator to e-commerce participant</field>
<field name="expression_ids">
<record id="tds_report_line_section_194o_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194O</field>
</record>
</field>
</record>
<record id="tds_report_line_section_194q" model="account.report.line">
<field name="name">Section 194Q: Purchase of goods</field>
<field name="expression_ids">
<record id="tds_report_line_section_194q_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-194Q</field>
</record>
</field>
</record>
<record id="tds_report_line_section_195" model="account.report.line">
<field name="name">Section 195: Payment of any other sum to a Non -resident</field>
<field name="expression_ids">
<record id="tds_report_line_section_195_tag" model="account.report.expression">
<field name="label">balance</field>
<field name="engine">tax_tags</field>
<field name="formula">-195</field>
</record>
</field>
</record>
</field>
</record>
</odoo>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="iap_service_l10n_in_edi" model="iap.service">
<field name="name">Indian EDI</field>
<field name="technical_name">l10n_in_edi</field>
<field name="description">Send electronic document to Indian government</field>
<field name="unit_name">Credits</field>
<field name="integer_balance">True</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,48 @@
"id","name","consider_amount","is_per_transaction_limit","per_transaction_limit","is_aggregate_limit","aggregate_limit","aggregate_period","tax_source_type","tax_report_line_id/id"
"tds_section_192","192","untaxed_amount","","","","","fiscal_yearly","tds","l10n_in.tds_report_line_section_192"
"tds_section_192a","192A","untaxed_amount","","","True","50000","fiscal_yearly","tds","l10n_in.tds_report_line_section_192a"
"tds_section_193","193","untaxed_amount","","","True","5000","fiscal_yearly","tds","l10n_in.tds_report_line_section_193"
"tds_section_194","194","untaxed_amount","","","True","5000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194"
"tds_section_194a","194A","untaxed_amount","","","True","5000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194a"
"tds_section_194b","194B","untaxed_amount","","","True","10000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194b"
"tds_section_194ba","194BA","untaxed_amount","","","True","0","fiscal_yearly","tds",""
"tds_section_194bb","194BB","untaxed_amount","","","True","10000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194bb"
"tds_section_194c","194C","untaxed_amount","True","30000","True","100000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194c"
"tds_section_194d","194D","untaxed_amount","","","True","15000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194d"
"tds_section_194da","194DA","untaxed_amount","","","True","100000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194da"
"tds_section_194e","194E","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_194e"
"tds_section_194ee","194EE","untaxed_amount","","","True","2500","fiscal_yearly","tds","l10n_in.tds_report_line_section_194ee"
"tds_section_194f","194F","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_194f"
"tds_section_194g","194G","untaxed_amount","","","True","15000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194g"
"tds_section_194h","194H","untaxed_amount","","","True","15000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194h"
"tds_section_194i","194I","untaxed_amount","","","True","240000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194i"
"tds_section_194ia","194IA","untaxed_amount","True","5000000","","","fiscal_yearly","tds","l10n_in.tds_report_line_section_194ia"
"tds_section_194ib","194IB","untaxed_amount","","","True","50000","monthly","tds","l10n_in.tds_report_line_section_194ib"
"tds_section_194ic","194IC","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_194ic"
"tds_section_194j","194J","untaxed_amount","","","True","30000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194j"
"tds_section_194j_dir","194J(DIRECTORS)","untaxed_amount","","","True","0","fiscal_yearly","tds",""
"tds_section_194k","194K","untaxed_amount","","","True","5000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194k"
"tds_section_194la","194LA","untaxed_amount","","","True","250000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194la"
"tds_section_194lba1","194LBA(1)","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_194lba"
"tds_section_194lbb","194LBB","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_194lbb"
"tds_section_194lb","194LB","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_194lb"
"tds_section_194lbc","194LBC","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_194lbc"
"tds_section_194m","194M","untaxed_amount","","","True","5000000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194m"
"tds_section_194n","194N","untaxed_amount","","","True","10000000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194n"
"tds_section_194o_huf","194O(HUF)","untaxed_amount","","","True","500000","fiscal_yearly","tds",""
"tds_section_194o","194O","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_194o"
"tds_section_194q","194Q","untaxed_amount","","","True","5000000","fiscal_yearly","tds","l10n_in.tds_report_line_section_194q"
"tds_section_195","195","untaxed_amount","","","True","0","fiscal_yearly","tds","l10n_in.tds_report_line_section_195"
"tcs_section_206c1_alc","206C(1) Liquor","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1_alfhc"
"tcs_section_206c1_tl","206C(1) Tendu leaves","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1_tl"
"tcs_section_206c1_tim","206C(1) Timber woods(FL)","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1_touafl"
"tcs_section_206c1_tim_o","206C(1) Timber woods","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1_tobaotuafl"
"tcs_section_206c1_fo","206C(1) OFP","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1_aofpnbtotl"
"tcs_section_206c1_sc","206C(1) Scrap","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1_s"
"tcs_section_206c1_min","206C(1) Min","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1_mbcoloio"
"tcs_section_206c1c_p","206C(1C) Parking lot","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1c_pl"
"tcs_section_206c1c_t","206C(1C) Toll plaza","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1c_tp"
"tcs_section_206c1c_mq","206C(1C) MQ","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1c_maq"
"tcs_section_206c1f_mv","206C(1F) Motor Vehicle","total_amount","True","1000000","","","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1f_mv"
"tcs_section_206c1g_r","206C(1G) Remittance","total_amount","","","True","700000","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1g_som"
"tcs_section_206c1g_ot","206C(1G) Overseas Tour","total_amount","","","True","0","fiscal_yearly","tcs","l10n_in.tcs_report_line_section_206c_1g_soaotpp"
1 id name consider_amount is_per_transaction_limit per_transaction_limit is_aggregate_limit aggregate_limit aggregate_period tax_source_type tax_report_line_id/id
2 tds_section_192 192 untaxed_amount fiscal_yearly tds l10n_in.tds_report_line_section_192
3 tds_section_192a 192A untaxed_amount True 50000 fiscal_yearly tds l10n_in.tds_report_line_section_192a
4 tds_section_193 193 untaxed_amount True 5000 fiscal_yearly tds l10n_in.tds_report_line_section_193
5 tds_section_194 194 untaxed_amount True 5000 fiscal_yearly tds l10n_in.tds_report_line_section_194
6 tds_section_194a 194A untaxed_amount True 5000 fiscal_yearly tds l10n_in.tds_report_line_section_194a
7 tds_section_194b 194B untaxed_amount True 10000 fiscal_yearly tds l10n_in.tds_report_line_section_194b
8 tds_section_194ba 194BA untaxed_amount True 0 fiscal_yearly tds
9 tds_section_194bb 194BB untaxed_amount True 10000 fiscal_yearly tds l10n_in.tds_report_line_section_194bb
10 tds_section_194c 194C untaxed_amount True 30000 True 100000 fiscal_yearly tds l10n_in.tds_report_line_section_194c
11 tds_section_194d 194D untaxed_amount True 15000 fiscal_yearly tds l10n_in.tds_report_line_section_194d
12 tds_section_194da 194DA untaxed_amount True 100000 fiscal_yearly tds l10n_in.tds_report_line_section_194da
13 tds_section_194e 194E untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_194e
14 tds_section_194ee 194EE untaxed_amount True 2500 fiscal_yearly tds l10n_in.tds_report_line_section_194ee
15 tds_section_194f 194F untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_194f
16 tds_section_194g 194G untaxed_amount True 15000 fiscal_yearly tds l10n_in.tds_report_line_section_194g
17 tds_section_194h 194H untaxed_amount True 15000 fiscal_yearly tds l10n_in.tds_report_line_section_194h
18 tds_section_194i 194I untaxed_amount True 240000 fiscal_yearly tds l10n_in.tds_report_line_section_194i
19 tds_section_194ia 194IA untaxed_amount True 5000000 fiscal_yearly tds l10n_in.tds_report_line_section_194ia
20 tds_section_194ib 194IB untaxed_amount True 50000 monthly tds l10n_in.tds_report_line_section_194ib
21 tds_section_194ic 194IC untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_194ic
22 tds_section_194j 194J untaxed_amount True 30000 fiscal_yearly tds l10n_in.tds_report_line_section_194j
23 tds_section_194j_dir 194J(DIRECTORS) untaxed_amount True 0 fiscal_yearly tds
24 tds_section_194k 194K untaxed_amount True 5000 fiscal_yearly tds l10n_in.tds_report_line_section_194k
25 tds_section_194la 194LA untaxed_amount True 250000 fiscal_yearly tds l10n_in.tds_report_line_section_194la
26 tds_section_194lba1 194LBA(1) untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_194lba
27 tds_section_194lbb 194LBB untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_194lbb
28 tds_section_194lb 194LB untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_194lb
29 tds_section_194lbc 194LBC untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_194lbc
30 tds_section_194m 194M untaxed_amount True 5000000 fiscal_yearly tds l10n_in.tds_report_line_section_194m
31 tds_section_194n 194N untaxed_amount True 10000000 fiscal_yearly tds l10n_in.tds_report_line_section_194n
32 tds_section_194o_huf 194O(HUF) untaxed_amount True 500000 fiscal_yearly tds
33 tds_section_194o 194O untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_194o
34 tds_section_194q 194Q untaxed_amount True 5000000 fiscal_yearly tds l10n_in.tds_report_line_section_194q
35 tds_section_195 195 untaxed_amount True 0 fiscal_yearly tds l10n_in.tds_report_line_section_195
36 tcs_section_206c1_alc 206C(1) Liquor total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1_alfhc
37 tcs_section_206c1_tl 206C(1) Tendu leaves total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1_tl
38 tcs_section_206c1_tim 206C(1) Timber woods(FL) total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1_touafl
39 tcs_section_206c1_tim_o 206C(1) Timber woods total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1_tobaotuafl
40 tcs_section_206c1_fo 206C(1) OFP total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1_aofpnbtotl
41 tcs_section_206c1_sc 206C(1) Scrap total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1_s
42 tcs_section_206c1_min 206C(1) Min total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1_mbcoloio
43 tcs_section_206c1c_p 206C(1C) Parking lot total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1c_pl
44 tcs_section_206c1c_t 206C(1C) Toll plaza total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1c_tp
45 tcs_section_206c1c_mq 206C(1C) MQ total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1c_maq
46 tcs_section_206c1f_mv 206C(1F) Motor Vehicle total_amount True 1000000 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1f_mv
47 tcs_section_206c1g_r 206C(1G) Remittance total_amount True 700000 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1g_som
48 tcs_section_206c1g_ot 206C(1G) Overseas Tour total_amount True 0 fiscal_yearly tcs l10n_in.tcs_report_line_section_206c_1g_soaotpp

View file

@ -1,61 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="account_reports_in_statements_menu" name="India" parent="account.menu_finance_reports" sequence="5"/>
<record id="indian_chart_template_standard" model="account.chart.template">
<field name="name">Indian Chart of Accounts - Standard</field>
<field name="bank_account_code_prefix">1002</field>
<field name="cash_account_code_prefix">1001</field>
<field name="transfer_account_code_prefix">1008</field>
<field name="code_digits">6</field>
<field name="currency_id" ref="base.INR"/>
<field name="country_id" ref="base.in"/>
</record>
<menuitem id="account_reports_in_statements_menu" name="India" parent="account.menu_finance_reports" sequence="6" groups="account.group_account_readonly"/>
<record id="sgst_tag_account" model="account.account.tag">
<field name="name">SGST</field>
<field name="applicability">accounts</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="cgst_tag_account" model="account.account.tag">
<field name="name">CGST</field>
<field name="applicability">accounts</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="igst_tag_account" model="account.account.tag">
<field name="name">IGST</field>
<field name="applicability">accounts</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="cess_tag_account" model="account.account.tag">
<field name="name">CESS</field>
<field name="applicability">accounts</field>
<field name="country_id" ref="base.in"/>
</record>
<record id="p10055" model="account.account.template">
<field name="name">CESS Receivable</field>
<field name="code">10055</field>
<field name="account_type">asset_current</field>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tag_ids" eval="[(6,0,[ref('cess_tag_account'),])]"/>
</record>
<record id="p10056" model="account.account.template">
<field name="name">Tax Receivable</field>
<field name="code">10056</field>
<field name="account_type">asset_current</field>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
</record>
<record model="account.account.template" id="p11235">
<field name="name">CESS Payable</field>
<field name="code">11235</field>
<field name="account_type">liability_current</field>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tag_ids" eval="[(6,0,[ref('cess_tag_account'),])]"/>
</record>
<record id="p11236" model="account.account.template">
<field name="name">Tax Payable</field>
<field name="code">11236</field>
<field name="account_type">liability_current</field>
<field name="reconcile" eval="False"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
</record>
</odoo>
</odoo>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="indian_chart_template_standard" model="account.chart.template">
<field name="property_account_receivable_id" ref="p10040"/>
<field name="property_account_payable_id" ref="p11211"/>
<field name="property_account_expense_categ_id" ref="p2107"/>
<field name="property_account_income_categ_id" ref="p20011"/>
<field name="property_tax_payable_account_id" ref="p11239"/>
<field name="property_tax_receivable_account_id" ref="p10059"/>
<field name="income_currency_exchange_account_id" ref="p2013"/>
<field name="expense_currency_exchange_account_id" ref="p2117"/>
<field name="default_pos_receivable_account_id" ref="p10041"/>
<field name="account_journal_early_pay_discount_loss_account_id" ref="p2132"/>
<field name="account_journal_early_pay_discount_gain_account_id" ref="2012"/>
</record>
</odoo>

View file

@ -0,0 +1,3 @@
UPDATE res_company
SET l10n_in_edi_production_env = false;

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data noupdate="1">
<record id="inter_state_group" model="res.country.group">
<field name="name">India Inter-State Group</field>
<field name="code">IN-INTER</field>
<field name="country_ids" eval="[Command.set([ref('base.in')])]"/>
<field name="exclude_state_ids" eval="[Command.set([ref('l10n_in.state_in_oc')])]"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="eco_under_section_52" model="res.partner.industry">
<field name="name">ECO liable to deduct TCS u/s 52</field>
<field name="full_name">E-Commerce operator liable to deduct TCS under section 52</field>
</record>
<record id="eco_under_section_9_5" model="res.partner.industry">
<field name="name">ECO liable to pay GST u/s 9(5)</field>
<field name="full_name">E-Commerce operator liable to pay tax under section 9(5)</field>
</record>
</odoo>

View file

@ -0,0 +1,103 @@
"id","name","code","account_type","tag_ids","reconcile","non_trade","l10n_in_tds_tcs_section_id"
"p10031","Inventories","10031","asset_current","","False","",""
"p10040","Debtors","10040","asset_receivable","","True","",""
"p10041","Debtors (PoS)","10041","asset_receivable","","True","",""
"p10051","SGST Receivable","10051","asset_current","l10n_in.sgst_tag_account","False","",""
"p10052","CGST Receivable","10052","asset_current","l10n_in.cgst_tag_account","False","",""
"p10053","IGST Receivable","10053","asset_current","l10n_in.igst_tag_account","False","",""
"p100531","IGST Paid on SEZ/Export Sales","100531","asset_current","l10n_in.igst_tag_account","False","",""
"p10054","IGST SEZ/Export Control Account","10054","asset_current","","False","",""
"p10057","Reverse Charge GST on Purchase","10057","asset_current","","False","",""
"p10058","TDS Receivable","10058","asset_current","","False","",""
"p10059","Tax Current Account - Receivable","10059","asset_receivable","","True","True",""
"p10061","Deposit Account","10061","asset_current","","False","",""
"p10071","Prepaid Insurance","10071","asset_current","","False","",""
"p1011","Buildings","1011","asset_fixed","","False","",""
"p1012","Land","1012","asset_fixed","","False","",""
"p1013","Equipment","1013","asset_fixed","","False","",""
"p1014","Vehicle","1014","asset_fixed","","False","",""
"p1015","Computer/Laptops (Assets)","1015","asset_fixed","","False","",""
"p1016","Furniture","1016","asset_fixed","","False","",""
"p1017","Air Conditionar","1017","asset_fixed","","False","",""
"p1018","Misc Assets","1018","asset_fixed","","False","",""
"p1111","Capital Account","1111","equity","","False","",""
"p1112","Reserve And Surplus Account","1112","liability_current","","False","",""
"p11211","Creditors","11211","liability_payable","","True","",""
"p11221","Bank OD Account","11221","liability_current","","False","",""
"p11222","Secured Loan Account","11222","liability_current","","False","",""
"p11223","Unsecured Loan Account","11223","liability_current","","False","",""
"p11231","TDS Payable","11231","liability_current","","False","",""
"p11232","SGST Payable","11232","liability_current","l10n_in.sgst_tag_account","False","",""
"p11233","CGST Payable","11233","liability_current","l10n_in.cgst_tag_account","False","",""
"p11234","IGST Payable","11234","liability_current","l10n_in.igst_tag_account","False","",""
"p112341","IGST SEZ/Export Control Account","112341","liability_current","","False","",""
"p112342","IGST Payable - Export","112342","liability_current","l10n_in.igst_tag_account","False","",""
"p11237","GST RCM Control Account","11237","liability_current","","False","",""
"p11239","Tax Current Account - Payable","11239","liability_payable","","True","True",""
"p11241","Wages Payable","11241","liability_current","","False","",""
"p11242","Interest Payable","11242","liability_current","","False","",""
"p11243","Notes Payable","11243","liability_current","","False","",""
"p20011","Local Sales","20011","income","","False","",""
"p20012","Retail Sales","20012","income","","False","",""
"p20013","Export Sales","20013","income","","False","",""
"p20021","Local Services","20021","income","","False","",""
"p20022","Export Services","20022","income","","False","",""
"p2010","Interest Revenues","2010","income","","False","",""
"p2011","Gain on Sale of Assets","2011","income","","False","",""
"2012","Write off Income","2012","income","","False","",""
"p2013","Foreign Exchange Profit","2013","income_other","","False","",""
"p2100","Electricity Expense","2100","expense","","False","",""
"p2101","Salary Expense","2101","expense","","False","",""
"p2102","Office Rent","2102","expense","","False","",""
"p2103","House Keeping Expense","2103","expense","","False","",""
"p2104","Postage And Courier Expense","2104","expense","","False","",""
"p2105","Internet Expense","2105","expense","","False","",""
"p2106","Telephone Expense","2106","expense","","False","",""
"p2107","Purchase Expense","2107","expense","","False","",""
"p2108","Computer/Laptop Accessories","2108","expense","","False","",""
"p2109","News Paper And Magazine","2109","expense","","False","",""
"p2110","Business Promotion","2110","expense","","False","",""
"p2111","Entertainment Expense","2111","expense","","False","",""
"p2112","Professional Services","2112","expense","","False","",""
"p2113","Bank Charges","2113","expense","","False","",""
"p2114","Diwali Bonus/Gift","2114","expense","","False","",""
"p2115","Parts Purchase","2115","expense","","False","",""
"p2116","Repairing Expense","2116","expense","","False","",""
"p2117","Foreign Exchange Loss","2117","expense","","False","",""
"p21181","Sales Commission Expense","21181","expense","","False","",""
"p21182","Stationary Expense","21182","expense","","False","",""
"p21183","Travelling Expense","21183","expense","","False","",""
"p2121","Opening Stock","2121","expense","","False","",""
"p2122","Purchase Stock","2122","expense","","False","",""
"p2123","Closing Stock","2123","expense","l10n_in.account_tag_closing_stock","False","",""
"p2131","Loss on Sale of Assets","2131","expense","","False","",""
"p2132","Write Off Expense","2132","expense","","False","",""
"p213201","Round off Expense","213201","expense","","False","",""
"p213202","Round off Income","213202","income","","False","",""
"p213300","Depreciation Account","213300","expense","","False","",""
"p11244","TDS Deducted","11244","liability_current","","False","",""
"p11245","TCS Collected","11245","liability_current","","False","",""
"p10055","CESS Receivable","10055","asset_current","l10n_in.cess_tag_account","False","",""
"p10056","Tax Receivable","10056","asset_current","","False","",""
"p11235","CESS Payable","11235","liability_current","l10n_in.cess_tag_account","False","",""
"p11236","Tax Payable","11236","liability_current","","False","",""
"p10084","Deferred Expenses","10084","asset_current","","False","",""
"p10085","Deferred Income","10085","liability_current","","False","",""
"p300001","House Rent Allowance Expense","300001","expense","","False","",""
"p300002","Other Allowance Expense","300002","expense","","False","",""
"p300003","Bonus to Employee Expense","300003","expense","","False","",""
"p300004","Supplementary Allowance Expense","300004","liability_current","","False","",""
"p300005","Performance Bonus","300005","liability_current","","False","",""
"p300006","Employee Reimbursement Expense","300006","liability_payable","","True","",""
"p300007","Provident fund - Employee Payable","300007","liability_current","","False","",""
"p300008","Provident fund - Employer Payable","300008","liability_current","","False","",""
"p300009","Advance to Employee","300009","liability_current","","False","",""
"p300010","Salary Exp Payable","300010","liability_current","","True","",""
"p300011","Leave Travel Allowance Expense","300011","liability_current","","False","",""
"p300012","Professional Tax Payable","300012","liability_current","","False","",""
"p100595","TDS (Withholding Control)","100595","asset_current","","False","",""
"p211210","Professional Fees","211210","expense","","False","","l10n_in.tds_section_194j"
"p211220","Audit Fees","211220","expense","","False","","l10n_in.tds_section_194j"
"p211230","Job Work Expense","211230","expense","","False","","l10n_in.tds_section_194c"
"p211240","Advertisement Expense","211240","expense","","False","","l10n_in.tds_section_194c"
"p211250","Commission/Brokerage Expense","211250","expense","","False","","l10n_in.tds_section_194h"
1 id name code account_type tag_ids reconcile non_trade l10n_in_tds_tcs_section_id
2 p10031 Inventories 10031 asset_current False
3 p10040 Debtors 10040 asset_receivable True
4 p10041 Debtors (PoS) 10041 asset_receivable True
5 p10051 SGST Receivable 10051 asset_current l10n_in.sgst_tag_account False
6 p10052 CGST Receivable 10052 asset_current l10n_in.cgst_tag_account False
7 p10053 IGST Receivable 10053 asset_current l10n_in.igst_tag_account False
8 p100531 IGST Paid on SEZ/Export Sales 100531 asset_current l10n_in.igst_tag_account False
9 p10054 IGST SEZ/Export Control Account 10054 asset_current False
10 p10057 Reverse Charge GST on Purchase 10057 asset_current False
11 p10058 TDS Receivable 10058 asset_current False
12 p10059 Tax Current Account - Receivable 10059 asset_receivable True True
13 p10061 Deposit Account 10061 asset_current False
14 p10071 Prepaid Insurance 10071 asset_current False
15 p1011 Buildings 1011 asset_fixed False
16 p1012 Land 1012 asset_fixed False
17 p1013 Equipment 1013 asset_fixed False
18 p1014 Vehicle 1014 asset_fixed False
19 p1015 Computer/Laptops (Assets) 1015 asset_fixed False
20 p1016 Furniture 1016 asset_fixed False
21 p1017 Air Conditionar 1017 asset_fixed False
22 p1018 Misc Assets 1018 asset_fixed False
23 p1111 Capital Account 1111 equity False
24 p1112 Reserve And Surplus Account 1112 liability_current False
25 p11211 Creditors 11211 liability_payable True
26 p11221 Bank OD Account 11221 liability_current False
27 p11222 Secured Loan Account 11222 liability_current False
28 p11223 Unsecured Loan Account 11223 liability_current False
29 p11231 TDS Payable 11231 liability_current False
30 p11232 SGST Payable 11232 liability_current l10n_in.sgst_tag_account False
31 p11233 CGST Payable 11233 liability_current l10n_in.cgst_tag_account False
32 p11234 IGST Payable 11234 liability_current l10n_in.igst_tag_account False
33 p112341 IGST SEZ/Export Control Account 112341 liability_current False
34 p112342 IGST Payable - Export 112342 liability_current l10n_in.igst_tag_account False
35 p11237 GST RCM Control Account 11237 liability_current False
36 p11239 Tax Current Account - Payable 11239 liability_payable True True
37 p11241 Wages Payable 11241 liability_current False
38 p11242 Interest Payable 11242 liability_current False
39 p11243 Notes Payable 11243 liability_current False
40 p20011 Local Sales 20011 income False
41 p20012 Retail Sales 20012 income False
42 p20013 Export Sales 20013 income False
43 p20021 Local Services 20021 income False
44 p20022 Export Services 20022 income False
45 p2010 Interest Revenues 2010 income False
46 p2011 Gain on Sale of Assets 2011 income False
47 2012 Write off Income 2012 income False
48 p2013 Foreign Exchange Profit 2013 income_other False
49 p2100 Electricity Expense 2100 expense False
50 p2101 Salary Expense 2101 expense False
51 p2102 Office Rent 2102 expense False
52 p2103 House Keeping Expense 2103 expense False
53 p2104 Postage And Courier Expense 2104 expense False
54 p2105 Internet Expense 2105 expense False
55 p2106 Telephone Expense 2106 expense False
56 p2107 Purchase Expense 2107 expense False
57 p2108 Computer/Laptop Accessories 2108 expense False
58 p2109 News Paper And Magazine 2109 expense False
59 p2110 Business Promotion 2110 expense False
60 p2111 Entertainment Expense 2111 expense False
61 p2112 Professional Services 2112 expense False
62 p2113 Bank Charges 2113 expense False
63 p2114 Diwali Bonus/Gift 2114 expense False
64 p2115 Parts Purchase 2115 expense False
65 p2116 Repairing Expense 2116 expense False
66 p2117 Foreign Exchange Loss 2117 expense False
67 p21181 Sales Commission Expense 21181 expense False
68 p21182 Stationary Expense 21182 expense False
69 p21183 Travelling Expense 21183 expense False
70 p2121 Opening Stock 2121 expense False
71 p2122 Purchase Stock 2122 expense False
72 p2123 Closing Stock 2123 expense l10n_in.account_tag_closing_stock False
73 p2131 Loss on Sale of Assets 2131 expense False
74 p2132 Write Off Expense 2132 expense False
75 p213201 Round off Expense 213201 expense False
76 p213202 Round off Income 213202 income False
77 p213300 Depreciation Account 213300 expense False
78 p11244 TDS Deducted 11244 liability_current False
79 p11245 TCS Collected 11245 liability_current False
80 p10055 CESS Receivable 10055 asset_current l10n_in.cess_tag_account False
81 p10056 Tax Receivable 10056 asset_current False
82 p11235 CESS Payable 11235 liability_current l10n_in.cess_tag_account False
83 p11236 Tax Payable 11236 liability_current False
84 p10084 Deferred Expenses 10084 asset_current False
85 p10085 Deferred Income 10085 liability_current False
86 p300001 House Rent Allowance Expense 300001 expense False
87 p300002 Other Allowance Expense 300002 expense False
88 p300003 Bonus to Employee Expense 300003 expense False
89 p300004 Supplementary Allowance Expense 300004 liability_current False
90 p300005 Performance Bonus 300005 liability_current False
91 p300006 Employee Reimbursement Expense 300006 liability_payable True
92 p300007 Provident fund - Employee Payable 300007 liability_current False
93 p300008 Provident fund - Employer Payable 300008 liability_current False
94 p300009 Advance to Employee 300009 liability_current False
95 p300010 Salary Exp Payable 300010 liability_current True
96 p300011 Leave Travel Allowance Expense 300011 liability_current False
97 p300012 Professional Tax Payable 300012 liability_current False
98 p100595 TDS (Withholding Control) 100595 asset_current False
99 p211210 Professional Fees 211210 expense False l10n_in.tds_section_194j
100 p211220 Audit Fees 211220 expense False l10n_in.tds_section_194j
101 p211230 Job Work Expense 211230 expense False l10n_in.tds_section_194c
102 p211240 Advertisement Expense 211240 expense False l10n_in.tds_section_194c
103 p211250 Commission/Brokerage Expense 211250 expense False l10n_in.tds_section_194h

View file

@ -0,0 +1,3 @@
"id","name","note","sequence"
"fiscal_position_in_reverse_charge_intra","Reverse charge Intra State","THE SUPPLY IS SUBJECT TO REVERSE CHARGE MECHANISM, SO THE RECIPIENT IS RESPONSIBLE FOR PAYING TAX.","10"
"fiscal_position_in_reverse_charge_inter","Reverse charge Inter State","THE SUPPLY IS SUBJECT TO REVERSE CHARGE MECHANISM, SO THE RECIPIENT IS RESPONSIBLE FOR PAYING TAX.","20"
1 id name note sequence
2 fiscal_position_in_reverse_charge_intra Reverse charge Intra State THE SUPPLY IS SUBJECT TO REVERSE CHARGE MECHANISM, SO THE RECIPIENT IS RESPONSIBLE FOR PAYING TAX. 10
3 fiscal_position_in_reverse_charge_inter Reverse charge Inter State THE SUPPLY IS SUBJECT TO REVERSE CHARGE MECHANISM, SO THE RECIPIENT IS RESPONSIBLE FOR PAYING TAX. 20

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
"id","name","country_id","sequence","tax_payable_account_id","tax_receivable_account_id"
"sgst_group","SGST/UTGST","base.in","","p11239","p10059"
"cgst_group","CGST","base.in","","p11239","p10059"
"igst_group","IGST","base.in","","p11239","p10059"
"cess_group","CESS","base.in","","p11239","p10059"
"gst_group","GST","base.in","","p11239","p10059"
"exempt_group","Exempt","base.in","","p11239","p10059"
"nil_rated_group","Nil Rated","base.in","","p11239","p10059"
"non_gst_supplies_group","Non GST Supplies","base.in","","p11239","p10059"
"tcs_group","TCS","base.in","100","p11239","p10059"
"tds_group","TDS","base.in","100","p11239","p10059"
1 id name country_id sequence tax_payable_account_id tax_receivable_account_id
2 sgst_group SGST/UTGST base.in p11239 p10059
3 cgst_group CGST base.in p11239 p10059
4 igst_group IGST base.in p11239 p10059
5 cess_group CESS base.in p11239 p10059
6 gst_group GST base.in p11239 p10059
7 exempt_group Exempt base.in p11239 p10059
8 nil_rated_group Nil Rated base.in p11239 p10059
9 non_gst_supplies_group Non GST Supplies base.in p11239 p10059
10 tcs_group TCS base.in 100 p11239 p10059
11 tds_group TDS base.in 100 p11239 p10059

View file

@ -36,7 +36,7 @@
<record id="uom.product_uom_cm" model="uom.uom">
<field name="l10n_in_code">CMS-CENTIMETERS</field>
</record>
<record id="uom.uom_square_meter" model="uom.uom">
<record id="uom.product_uom_square_meter" model="uom.uom">
<field name="l10n_in_code">SQM-SQUARE METERS</field>
</record>
<record id="uom.product_uom_litre" model="uom.uom">
@ -63,7 +63,7 @@
<record id="uom.product_uom_mile" model="uom.uom">
<field name="l10n_in_code">OTH-OTHERS</field>
</record>
<record id="uom.uom_square_foot" model="uom.uom">
<record id="uom.product_uom_square_foot" model="uom.uom">
<field name="l10n_in_code">SQF-SQUARE FEET</field>
</record>
<record id="uom.product_uom_floz" model="uom.uom">
@ -81,4 +81,7 @@
<record id="uom.product_uom_cubic_foot" model="uom.uom">
<field name="l10n_in_code">OTH-OTHERS</field>
</record>
<record id="uom.product_uom_milliliter" model="uom.uom">
<field name="l10n_in_code">MLT-MILILITRE</field>
</record>
</odoo>

View file

@ -1,15 +1,623 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
import logging
import time
from datetime import datetime, timedelta
from odoo import api, models, Command
from odoo.exceptions import UserError, ValidationError
from odoo.tools.misc import file_open
_logger = logging.getLogger(__name__)
class AccountChartTemplate(models.AbstractModel):
_inherit = "account.chart.template"
@api.model
def _get_demo_data(self):
company = self.env.company
def _get_demo_data(self, company=False):
demo_data = {}
if company.account_fiscal_country_id.code == "IN":
if company.state_id.country_id.code != "IN":
company.state_id = self.env.ref("base.state_in_gj")
if company.country_id.code != "IN":
company.country_id = self.env.ref("base.in")
return super()._get_demo_data()
if company.state_id:
company.write({
'l10n_in_is_gst_registered': True,
'l10n_in_tcs_feature': True,
'l10n_in_tds_feature': True,
'l10n_in_edi_production_env': False,
})
demo_data = {
'res.partner.category': self._get_demo_data_res_partner_category(company),
'res.partner': self._get_demo_data_partner(),
'account.move': self._get_demo_data_move(company),
'res.config.settings': self._get_demo_data_config_settings(company),
'ir.attachment': self._get_demo_data_attachment(company),
'mail.message': self._get_demo_data_mail_message(company),
}
else:
_logger.warning('Error while loading Indian-Accounting demo data in the company "%s".State is not set in the company.', company.name)
else:
demo_data = super()._get_demo_data(company)
return demo_data
@api.model
def _get_demo_data_config_settings(self, company=False):
return{
'sales_credit_limit':{
'account_use_credit_limit': True,
'account_default_credit_limit': '10000'
}
}
@api.model
def _get_demo_data_res_partner_category(self, company=False):
return{
'res_partner_category_registered': {
'name': 'Registered',
'color': 2,
},
'res_partner_category_unregistered': {
'name': 'Unregistered',
'color': 3,
},
}
@api.model
def _get_demo_data_partner(self):
company = self.env.company
if company.account_fiscal_country_id.code != "IN" or not company.state_id:
return super()._get_demo_data_partner()
inter_state_ref = 'base.state_in_ts'
intra_state_ref = 'base.state_in_gj'
default_partner_dict = {'country_id': 'base.in', 'is_company': True, 'company_id': company.id}
return{
'res_partner_registered_customer': {
**default_partner_dict,
'name': 'B2B Customer Intra State',
'category_id': 'res_partner_category_registered',
'l10n_in_gst_treatment': 'regular',
'street': '201, Second Floor, IT Tower 4',
'street2': 'InfoCity Gate - 1, Infocity',
'city': 'Gandhinagar',
'state_id': 'base.state_in_gj',
'zip': '382010',
'vat': '24AABCT1332L2ZD',
},
'res_partner_registered_customer_inter_state': {
**default_partner_dict,
'name': 'B2B Customer Inter State',
'category_id': 'res_partner_category_registered',
'l10n_in_gst_treatment': 'regular',
'street': 'floor-1, Maddikunta-Ankanpally Village',
'street2': 'Post box No 2, NH-65',
'city': 'Hyderabad',
'state_id': inter_state_ref,
'zip': '500014',
'vat': '36AAACM4154G1ZO',
},
'res_partner_unregistered_customer':{
**default_partner_dict,
'name': 'B2C Customer Intra State',
'category_id': 'res_partner_category_unregistered',
'l10n_in_gst_treatment': 'unregistered',
'street': 'B105, yogeshwar Tower',
'state_id': intra_state_ref,
'city': 'Rajkot',
'zip': '360001'
},
'res_partner_unregistered_customer_inter_state':{
**default_partner_dict,
'name': 'B2C Customer Inter State',
'category_id': 'res_partner_category_unregistered',
'l10n_in_gst_treatment': 'unregistered',
'street': '80, Sarojini Devi Road',
'city': 'Hyderabad',
'state_id': inter_state_ref,
'zip': '500003'
},
'res_partner_registered_supplier_1': {
**default_partner_dict,
'name': 'Supplier',
'category_id': 'res_partner_category_registered',
'l10n_in_gst_treatment': 'regular',
'street': '19, Ground Floor',
'street2': 'Survey Road,Vadipatti',
'city': 'Madurai',
'state_id': 'base.state_in_tn',
'zip': '625218',
'vat': '33AACCT6304M1DB',
},
'res_partner_registered_supplier_2': {
**default_partner_dict,
'name': 'Odoo In Private Limited',
'category_id': 'res_partner_category_registered',
'l10n_in_gst_treatment': 'regular',
'street': '401, Fourth Floor, IT Tower 4',
'street2': 'InfoCity Gate - 1, Infocity',
'city': 'Hyderabad',
'state_id': inter_state_ref,
'zip': '500014',
'vat': '36AACCT6304M1ZB',
},
'res_partner_overseas': {
'name': 'Supplier Overseas',
'l10n_in_gst_treatment': 'overseas',
'street': '142 Street, Rigas building',
'street2': 'Survey Road,',
'city': 'City',
'zip': '000000',
'state_id': 'base.state_us_5',
'country_id': 'base.us',
'is_company': True,
'company_id': company.id,
},
}
@api.model
def _get_demo_data_move(self, company=False):
cid = company.id or self.env.company.id
def _get_tax_by_id(tax_id):
tax = self.env.ref('account.%s_%s'%((cid), (tax_id)))
return tax.id
if company.account_fiscal_country_id.code == "IN":
sale_journal = self.env['account.journal'].search(
domain=[
*self.env['account.journal']._check_company_domain(cid),
('type', '=', 'sale'),
], limit=1)
return {
# Demo of B2B (business-to-business) Taxable supplies made to other registered person.
self.company_xmlid('demo_invoice_b2b_1'): {
'move_type': 'out_invoice',
'partner_id': 'res_partner_registered_customer',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'regular',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.product_product_8',
'quantity': 2,
'price_unit': 40000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_28')])],
}),
Command.create({
'product_id': 'product.product_product_9',
'quantity': 3,
'price_unit': 400.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_28'), _get_tax_by_id('cess_5_plus_1591_sale')])],
}),
Command.create({
'product_id': 'product.product_product_10',
'quantity': 4,
'price_unit': 300.0,
'tax_ids':[Command.set([_get_tax_by_id('sgst_sale_18')])],
}),
],
},
self.company_xmlid('demo_invoice_b2b_2'): {
'move_type': 'out_invoice',
'partner_id': 'res_partner_registered_customer_inter_state',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'regular',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.product_product_9',
'quantity': 2,
'price_unit': 4000.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_sale_5')])],
}),
Command.create({
'product_id': 'product.product_product_10',
'quantity': 3,
'price_unit': 300.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_sale_5')])],
}),
],
},
self.company_xmlid('demo_bill_b2b_1'): {
'ref': 'INV/001',
'move_type': 'in_invoice',
'partner_id': 'res_partner_registered_supplier_2',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'invoice_line_ids': [
Command.create({
'product_id': 'product.consu_delivery_01',
'quantity': 1,
'price_unit': 1000.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_purchase_18')])],
}),
Command.create({
'product_id': 'product.consu_delivery_03',
'quantity': 1,
'price_unit': 2000.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_purchase_18')])],
}),
]
},
self.company_xmlid('demo_bill_b2b_2'): {
'ref': 'INV/002',
'move_type': 'in_invoice',
'partner_id': 'res_partner_registered_supplier_2',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'invoice_line_ids': [
Command.create({
'product_id': 'product.consu_delivery_01',
'quantity': 4,
'price_unit': 1000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_purchase_18')])],
}),
Command.create({
'product_id': 'product.consu_delivery_03',
'quantity': 3,
'price_unit': 2000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_purchase_18')])],
}),
]
},
self.company_xmlid('demo_bill_b2b_3'): {
'ref': 'INV/003',
'move_type': 'in_invoice',
'partner_id': 'res_partner_registered_supplier_1',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'invoice_line_ids': [
Command.create({
'product_id': 'product.consu_delivery_01',
'quantity': 2,
'price_unit': 1000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_purchase_18')])],
}),
Command.create({
'product_id': 'product.consu_delivery_03',
'quantity': 3,
'price_unit': 2000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_purchase_18')])],
}),
]
},
self.company_xmlid('demo_invoice_to_extract'): {
'move_type': 'in_invoice',
'message_main_attachment_id': 'ir_attachment_in_invoice_1',
},
self.company_xmlid('demo_invoice_service'): {
'ref': 'MYS-91021146',
'move_type': 'in_invoice',
'partner_id': 'res_partner_registered_supplier_2',
'invoice_user_id': False,
'invoice_date': datetime.now(),
'invoice_line_ids': [
Command.create({
'name': 'Integrated Managed Infrastructure Service',
'quantity': 1,
'price_unit': 69132.78,
'tax_ids': [Command.set([_get_tax_by_id('sgst_purchase_18')])],
}),
],
'message_main_attachment_id': 'ir_attachment_in_invoice_2',
},
# Demo of IMP(Import) of supplies.
self.company_xmlid('demo_bill_imp'): {
'ref': 'BOE/123',
'move_type': 'in_invoice',
'partner_id': 'res_partner_overseas',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'invoice_line_ids': [
Command.create({
'product_id': 'product.product_product_4',
'quantity': 30,
'price_unit': 9000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_purchase_18')])],
}),
]
},
# Demo of cdnr(Credit/ Debit Note for registered business). Create credit note for demo b2b bill.
self.company_xmlid('demo_bill_cdnr_1'): {
'ref': 'CR/001',
'move_type': 'in_refund',
'partner_id': 'res_partner_registered_supplier_2',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now() - timedelta(days=1),
'l10n_in_gst_treatment': 'regular',
'invoice_line_ids': [
Command.create({
'product_id': 'product.consu_delivery_01',
'quantity': 1,
'price_unit': 1000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_purchase_18')])],
}),
Command.create({
'product_id': 'product.consu_delivery_03',
'quantity': 1,
'price_unit': 2000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_purchase_18')])],
}),
]
},
self.company_xmlid('demo_bill_cdnr_2'): {
'ref': '000072',
'move_type': 'in_refund',
'partner_id': 'res_partner_registered_supplier_1',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'regular',
'invoice_line_ids': [
Command.create({
'product_id': 'product.consu_delivery_01',
'quantity': 1,
'price_unit': 1000.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_purchase_18')])],
}),
]
},
# Demo of B2CS (business to consumer small) Taxable supplies made to other unregistered Person and below INR 2.5 lakhs invoice value.
self.company_xmlid('demo_invoice_b2cs'): {
'move_type': 'out_invoice',
'partner_id': 'res_partner_unregistered_customer_inter_state',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'consumer',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.product_product_16',
'quantity': 1,
'price_unit': 1500.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_sale_18')])],
}),
Command.create({
'product_id': 'product.product_product_20',
'quantity': 1,
'price_unit': 2300.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_sale_18')])],
}),
Command.create({
'product_id': 'product.product_product_22',
'quantity': 1,
'price_unit': 2600.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_sale_18')])],
}),
Command.create({
'product_id': 'product.product_product_24',
'quantity': 2,
'price_unit': 1655.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_sale_5')])],
}),
]
},
# Demo of B2CL (business to consumer - Large) Taxable supplies made to other unregistered Person and invoice value is more than INR 2.5 lakhs.
self.company_xmlid('demo_invoice_b2cl'): {
'move_type': 'out_invoice',
'partner_id': 'res_partner_unregistered_customer',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'consumer',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.consu_delivery_01',
'quantity': 3,
'price_unit': 90000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_18')])],
}),
]
},
# Demo of EXP(Export) supplies including supplies to SEZ/SEZ Developer or deemed exports.
self.company_xmlid('demo_invoice_exp'): {
'move_type': 'out_invoice',
'partner_id': 'base.res_partner_3',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'overseas',
'l10n_in_shipping_bill_number': '999704',
'l10n_in_shipping_bill_date': time.strftime('%Y-%m-02'),
'l10n_in_shipping_port_code_id': 'l10n_in.port_code_inixy1',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.product_product_4',
'quantity': 30,
'price_unit': 8000.0,
'tax_ids': [Command.set([_get_tax_by_id('igst_sale_18_sez_exp')])],
}),
]
},
# Demo of exempt(Nil Rated, Exempted and Non GST supplies). Set Nill rated and Exempted tax in line.
self.company_xmlid('demo_invoice_nill'): {
'move_type': 'out_invoice',
'partner_id': 'res_partner_registered_customer',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'regular',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.product_product_1',
'quantity': 2,
'price_unit': 25000.0,
'tax_ids': [Command.set([_get_tax_by_id('exempt_sale')])],
}),
Command.create({
'product_id': 'product.product_product_5',
'quantity': 1,
'price_unit': 400.0,
'tax_ids': [Command.set([_get_tax_by_id('nil_rated_sale')])],
}),
]
},
# Demo of cdnr(Credit/ Debit Note for registered person). Create credit note for demo b2b invoice.
self.company_xmlid('demo_invoice_cdnr_1'): {
'move_type': 'out_refund',
'partner_id': 'res_partner_registered_customer',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'regular',
'reversed_entry_id': 'demo_invoice_b2b_1',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.product_product_8',
'quantity': 2,
'price_unit': 40000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_28')])],
}),
Command.create({
'product_id': 'product.product_product_9',
'quantity': 3,
'price_unit': 400.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_28'), _get_tax_by_id('cess_5_plus_1591_sale')])],
}),
Command.create({
'product_id': 'product.product_product_10',
'quantity': 4,
'price_unit': 300.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_18')])],
}),
]
},
self.company_xmlid('demo_invoice_cdnr_2'): {
'move_type': 'out_refund',
'partner_id': 'res_partner_registered_customer',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'regular',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.consu_delivery_01',
'quantity': 1,
'price_unit': 1000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_18')])],
}),
Command.create({
'product_id': 'product.consu_delivery_03',
'quantity': 1,
'price_unit': 2000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_18')])],
}),
]
},
# Demo of cdnr(Credit/ Debit Note for unregistered person). Create credit note for demo b2cl invoice.
self.company_xmlid('demo_invoice_cdnur'): {
'move_type': 'out_refund',
'partner_id': 'res_partner_unregistered_customer',
'invoice_user_id': 'base.user_demo',
'invoice_payment_term_id': 'account.account_payment_term_end_following_month',
'invoice_date': datetime.now(),
'l10n_in_gst_treatment': 'consumer',
'reversed_entry_id': 'demo_invoice_b2cl',
'journal_id': sale_journal.id,
'invoice_line_ids': [
Command.create({
'product_id': 'product.consu_delivery_01',
'quantity': 3,
'price_unit': 90000.0,
'tax_ids': [Command.set([_get_tax_by_id('sgst_sale_18')])],
}),
]
},
}
else:
return super()._get_demo_data_move(company)
@api.model
def _get_demo_data_attachment(self, company=False):
if company.account_fiscal_country_id.code == "IN":
return{
'ir_attachment_in_invoice_1': {
'type': 'binary',
'name': 'in_invoice_demo_1.pdf',
'res_model': 'account.move',
'res_id': 'demo_invoice_to_extract',
'raw': file_open(
'l10n_in/static/demo/in_invoice_demo_1.pdf', 'rb'
).read()
},
'ir_attachment_in_invoice_2': {
'type': 'binary',
'name': 'in_invoice_demo_2.pdf',
'res_model': 'account.move',
'res_id': 'demo_invoice_service',
'raw': file_open(
'l10n_in/static/demo/in_invoice_demo_2.pdf', 'rb'
).read()
}
}
else:
return super()._get_demo_data_attachment(company)
@api.model
def _get_demo_data_mail_message(self, company=False):
if company.account_fiscal_country_id.code == "IN":
return {
'mail_message_in_invoice_1': {
'model': 'account.move',
'res_id': 'demo_invoice_to_extract',
'body': 'Vendor Bill attachment',
'message_type': 'comment',
'author_id': 'base.partner_demo',
'attachment_ids': [Command.set([
'ir_attachment_in_invoice_1',
])]
},
'mail_message_in_invoice_2': {
'model': 'account.move',
'res_id': 'demo_invoice_service',
'body': 'Vendor Bill attachment',
'message_type': 'comment',
'author_id': 'base.partner_demo',
'attachment_ids': [Command.set([
'ir_attachment_in_invoice_2',
])]
},
}
else:
return super()._get_demo_data_mail_message(company)
def _post_load_demo_data(self, company=False):
if company.account_fiscal_country_id.code == "IN":
if company.state_id:
invoices = (
self.ref('demo_invoice_b2b_1')
+ self.ref('demo_invoice_b2b_2')
+ self.ref('demo_invoice_b2cs')
+ self.ref('demo_invoice_b2cl')
+ self.ref('demo_invoice_exp')
+ self.ref('demo_invoice_nill')
+ self.ref('demo_invoice_cdnr_1')
+ self.ref('demo_invoice_cdnr_2')
+ self.ref('demo_invoice_cdnur')
+ self.ref('demo_bill_b2b_1')
+ self.ref('demo_bill_b2b_2')
+ self.ref('demo_bill_b2b_3')
+ self.ref('demo_bill_imp')
+ self.ref('demo_bill_cdnr_1')
+ self.ref('demo_bill_cdnr_2')
+ self.ref('demo_invoice_service')
)
for move in invoices:
try:
move.action_post()
except (UserError, ValidationError):
_logger.exception('Error while posting demo data')
else:
return super()._post_load_demo_data(company)

View file

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="demo_invoice_b2b_intra_state" model="account.move">
<field name="move_type">out_invoice</field>
<field name="partner_id" ref="l10n_in.res_partner_registered_customer_intra_state"/>
<field name="l10n_in_reseller_partner_id" ref="l10n_in.res_partner_reseller"/>
<field name="invoice_user_id" ref="base.user_demo"/>
<field name="invoice_payment_term_id" ref="account.account_payment_term_end_following_month"/>
<field name="invoice_date" eval="time.strftime('%Y-%m')+'-01'"/>
<field name="l10n_in_gst_treatment">regular</field>
<field name="journal_id" model="account.journal"
eval="obj().search([
('type', '=', 'sale'),
('company_id', '=', ref('l10n_in.demo_company_in'))], limit=1).id"/>
<field name="invoice_line_ids" model="account.move.line" eval="[
(0, 0, {
'product_id': ref('product.product_product_8'),
'quantity': 2,
'price_unit': 40000.0,
'tax_ids': [(6, 0, obj().tax_ids.search([
('company_id', '=', ref('l10n_in.demo_company_in')),
('type_tax_use', '=', 'sale'),
('amount','=', 28),
('tax_group_id', '=', ref('l10n_in.gst_group'))], limit=1).ids)]
}),
(0, 0, {
'product_id': ref('product.product_product_9'),
'quantity': 3,
'price_unit': 400.0,
'tax_ids': [(6, 0, obj().tax_ids.search([
('company_id', '=', ref('l10n_in.demo_company_in')),
('type_tax_use', '=', 'sale'),
('amount','=', 18),
('tax_group_id', '=', ref('l10n_in.gst_group'))], limit=1).ids)]
}),
(0, 0, {
'product_id': ref('product.product_product_10'),
'quantity': 4,
'price_unit': 300.0,
'tax_ids': [(6, 0, obj().tax_ids.search([
('company_id', '=', ref('l10n_in.demo_company_in')),
('type_tax_use', '=', 'sale'),
'|',
'&amp;',
('amount', '=', 18),
('tax_group_id', '=', ref('l10n_in.gst_group')),
'&amp;',
('tax_group_id', '=', ref('l10n_in.cess_group')),
('children_tax_ids.amount','=', 5)
], limit=2).ids)]
}),
]"/>
</record>
</odoo>

View file

@ -1,35 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="partner_demo_company_in" model="res.partner">
<record id="base.partner_demo_company_in" model="res.partner" forcecreate="1">
<field name="name">IN Company</field>
<field name="vat">36AABCT1332L011</field>
<field name="vat">24FANCY1234AAZA</field>
<field name="l10n_in_tan">GANI12345A</field>
<field name="street">Block no. 401</field>
<field name="street2">Street 2</field>
<field name="city">Hyderabad</field>
<field name="city">Gandhinagar</field>
<field name="country_id" ref="base.in"/>
<field name="state_id" ref="base.state_in_ts"/>
<field name="zip">500001</field>
<field name="state_id" ref="base.state_in_gj"/>
<field name="zip">382002</field>
<field name="phone">+91 81234 56789</field>
<field name="email">info@company.inexample.com</field>
<field name="website">www.inexample.com</field>
<field name="is_company" eval="True"/>
</record>
<record id="demo_company_in" model="res.company">
<record id="base.demo_company_in" model="res.company" forcecreate="1">
<field name="name">IN Company</field>
<field name="partner_id" ref="partner_demo_company_in"/>
<field name="partner_id" ref="base.partner_demo_company_in"/>
</record>
<function model="res.company" name="_onchange_country_id">
<value eval="[ref('demo_company_in')]"/>
<value eval="[ref('base.demo_company_in')]"/>
</function>
<function model="res.users" name="write">
<value eval="[ref('base.user_root'), ref('base.user_admin'), ref('base.user_demo')]"/>
<value eval="{'company_ids': [(4, ref('l10n_in.demo_company_in'))]}"/>
<value eval="{'company_ids': [(4, ref('base.demo_company_in'))]}"/>
</function>
<function model="account.chart.template" name="try_loading">
<value eval="[ref('l10n_in.indian_chart_template_standard')]"/>
<value model="res.company" eval="obj().env.ref('l10n_in.demo_company_in')"/>
<value eval="[]"/>
<value>in</value>
<value model="res.company" eval="obj().env.ref('base.demo_company_in')"/>
<value name="install_demo" eval="True"/>
</function>
</odoo>

View file

@ -2,127 +2,96 @@
<odoo noupdate="1">
<record id="product.product_product_1" model="product.product">
<field name="l10n_in_hsn_code">998391</field>
<field name="l10n_in_hsn_description">Specialty Design Services Including Interior Design, Fashion Design, Industrial Design And Other Specialty Design Services</field>
</record>
<record id="product.product_product_2" model="product.product">
<field name="l10n_in_hsn_code">998391</field>
<field name="l10n_in_hsn_description">Specialty Design Services Including Interior Design, Fashion Design, Industrial Design And Other Specialty Design Services</field>
</record>
<record id="product.product_product_3" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_4" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_4b" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_4c" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_5" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_6" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_7" model="product.product">
<field name="l10n_in_hsn_code">48196000</field>
<field name="l10n_in_hsn_description">Box files, letter trays, storage boxes and similar articles, of a kind used in offices, shops or the like</field>
</record>
<record id="product.product_product_8" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_9" model="product.product">
<field name="l10n_in_hsn_code">7323</field>
<field name="l10n_in_hsn_description">Table, kitchen or other household articles and parts thereof, of iron or steel; iron or steel wool; pot scourers and scouring or polishing pads, gloves and the like, of iron or steel.</field>
</record>
<record id="product.product_product_10" model="product.product">
<field name="l10n_in_hsn_code">84185000</field>
<field name="l10n_in_hsn_description">Other furniture (chests, cabinets, display counters, show-cases and the like) for storage and display, incorporating refrigerating or freezing equipment</field>
</record>
<record id="product.product_product_11" model="product.product">
<field name="l10n_in_hsn_code">94018000</field>
<field name="l10n_in_hsn_description">Seats (other than those of heading 9402), whether or not convertible into beds, and parts thereof</field>
</record>
<record id="product.product_product_11b" model="product.product">
<field name="l10n_in_hsn_code">94018000</field>
<field name="l10n_in_hsn_description">Seats (other than those of heading 9402), whether or not convertible into beds, and parts thereof</field>
</record>
<record id="product.product_product_12" model="product.product">
<field name="l10n_in_hsn_code">94018000</field>
<field name="l10n_in_hsn_description">Seats (other than those of heading 9402), whether or not convertible into beds, and parts thereof</field>
</record>
<record id="product.product_product_13" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_16" model="product.product">
<field name="l10n_in_hsn_code">94031090</field>
<field name="l10n_in_hsn_description">Metal furniture of a kind used in offices</field>
</record>
<record id="product.product_product_20" model="product.product">
<field name="l10n_in_hsn_code">37011090</field>
<field name="l10n_in_hsn_description">Photographic plates and film in the flat, sensitised, unexposed, of any material other than paper, paperboard or textiles; instant print film in the flat, sensitised, unexposed, whether or not in packs.</field>
</record>
<record id="product.product_product_22" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
<record id="product.product_product_24" model="product.product">
<field name="l10n_in_hsn_code">94031090</field>
<field name="l10n_in_hsn_description">Metal furniture of a kind used in offices</field>
</record>
<record id="product.product_product_25" model="product.product">
<field name="l10n_in_hsn_code">94031090</field>
<field name="l10n_in_hsn_description">Metal furniture of a kind used in offices</field>
</record>
<record id="product.product_product_27" model="product.product">
<field name="l10n_in_hsn_code">94031090</field>
<field name="l10n_in_hsn_description">Metal furniture of a kind used in offices</field>
</record>
<!-- Expensable products -->
<record id="product.expense_product" model="product.product">
<field name="l10n_in_hsn_code">9963.31</field>
<field name="l10n_in_hsn_description">Services provided by Restaurants, Cafes and similar eating facilities including takeaway services, Room services and door delivery of food.
</field>
<field name="l10n_in_hsn_code">996331</field>
</record>
<record id="product.expense_hotel" model="product.product">
<field name="l10n_in_hsn_code">9963.32</field>
<field name="l10n_in_hsn_description">Services provided by Hotels, INN, Guest House, Club etc including Room services, takeaway services and door delivery of food.</field>
<field name="l10n_in_hsn_code">996332</field>
</record>
<!-- Physical Products -->
<record id="product.product_delivery_01" model="product.product">
<field name="l10n_in_hsn_code">94018000</field>
<field name="l10n_in_hsn_description">Seats (other than those of heading 9402), whether or not convertible into beds, and parts thereof</field>
</record>
<record id="product.product_delivery_02" model="product.product">
<field name="l10n_in_hsn_code">94051090</field>
<field name="l10n_in_hsn_description">Lamps and lighting fittings including searchlights and spotlights and parts thereof, not elsewhere specified or included; illuminated signs, illuminated name-plates and the like, having a permanently fixed light source, and parts thereof not elsewhere specified or included</field>
</record>
<record id="product.product_order_01" model="product.product">
<field name="l10n_in_hsn_code">4911.99.10</field>
<field name="l10n_in_hsn_description">Hard copy (printed) of computer software</field>
<field name="l10n_in_hsn_code">49119910</field>
</record>
<record id="product.consu_delivery_01" model="product.product">
<field name="l10n_in_hsn_code">9401.61.00</field>
<field name="l10n_in_hsn_description">Seats (other than those of heading 9402), whether or not convertible into beds, and parts thereof</field>
<field name="l10n_in_hsn_code">94016100</field>
</record>
<record id="product.consu_delivery_02" model="product.product">
<field name="l10n_in_hsn_code">9403.89.00</field>
<field name="l10n_in_hsn_description">Other Furniture</field>
<field name="l10n_in_hsn_code">94038900</field>
</record>
<record id="product.consu_delivery_03" model="product.product">
<field name="l10n_in_hsn_code">9403</field>
<field name="l10n_in_hsn_description">Other furniture and parts thereof.</field>
</record>
</odoo>

View file

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="res_partner_category_registered" model="res.partner.category">
<field name="name">Registered</field>
<field name="color" eval="2"/>
</record>
<record id="res_partner_category_unregistered" model="res.partner.category">
<field name="name">Unregistered</field>
<field name="color" eval="3"/>
</record>
<record id="res_partner_category_reseller" model="res.partner.category">
<field name="name">Reseller</field>
<field name="color" eval="12"/>
</record>
<record id="res_partner_registered_customer_intra_state" model="res.partner">
<field name="name">Registered Customer Intra State</field>
<field eval="[(6, 0, [ref('l10n_in.res_partner_category_registered')])]" name="category_id"/>
<field name="is_company">1</field>
<field name="l10n_in_gst_treatment">regular</field>
<field name="street">floor-1, Maddikunta-Ankanpally Village</field>
<field name="street2">Post box No 2, NH-65</field>
<field name="city">Sangareddy</field>
<field name="zip">500002</field>
<field name="state_id" ref="base.state_in_ts"/>
<field name="country_id" ref="base.in"/>
<field name="vat">36AAACM4154G1ZO</field>
</record>
<!-- reseller partner -->
<record id="res_partner_reseller" model="res.partner">
<field name="name">Reseller(E-Commerce)</field>
<field eval="[(6, 0, [ref('l10n_in.res_partner_category_reseller'),
ref('l10n_in.res_partner_category_registered')])]" name="category_id"/>
<field name="street">4/001 Ground Floor, 16th Main Rd,</field>
<field name="l10n_in_gst_treatment">regular</field>
<field name="city">Bengaluru</field>
<field name="zip">560001</field>
<field name="state_id" ref="base.state_in_ka"/>
<field name="country_id" ref="base.in"/>
<field name="vat">29AJIPA1572E1ZR</field>
</record>
</odoo>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
import odoo
from odoo.modules.registry import Registry
def migrate(cr, version):
registry = odoo.registry(cr.dbname)
registry = Registry(cr.dbname)
from odoo.addons.account.models.chart_template import migrate_set_tags_and_taxes_updatable
migrate_set_tags_and_taxes_updatable(cr, registry, 'l10n_in')

View file

@ -1,13 +1,19 @@
# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account
from . import template_in
from . import account_invoice
from . import chart_template
from . import account_journal
from . import account_move_line
from . import account_payment
from . import account_tax
from . import company
from . import iap_account
from . import product_template
from . import port_code
from . import res_config_settings
from . import res_country_state
from . import res_partner
from . import uom_uom
from . import mail_message
from . import account_account
from . import l10n_in_section_alert
from . import l10n_in_pan_entity
from . import l10n_in_report_handler

View file

@ -1,77 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo import tools
class AccountJournal(models.Model):
_inherit = "account.journal"
# Use for filter import and export type.
l10n_in_gstin_partner_id = fields.Many2one('res.partner', string="GSTIN Unit", ondelete="restrict", help="GSTIN related to this journal. If empty then consider as company GSTIN.")
def name_get(self):
"""
Add GSTIN number in name as suffix so user can easily find the right journal.
Used super to ensure nothing is missed.
"""
result = super().name_get()
result_dict = dict(result)
indian_journals = self.filtered(lambda j: j.company_id.account_fiscal_country_id.code == 'IN' and
j.l10n_in_gstin_partner_id and j.l10n_in_gstin_partner_id.vat)
for journal in indian_journals:
name = result_dict[journal.id]
name += "- %s" % (journal.l10n_in_gstin_partner_id.vat)
result_dict[journal.id] = name
return list(result_dict.items())
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
def init(self):
tools.create_index(self._cr, 'account_move_line_move_product_index', self._table, ['move_id', 'product_id'])
@api.depends('move_id.line_ids', 'move_id.line_ids.tax_line_id', 'move_id.line_ids.debit', 'move_id.line_ids.credit')
def _compute_tax_base_amount(self):
aml = self.filtered(lambda l: l.company_id.account_fiscal_country_id.code == 'IN' and l.tax_line_id and l.product_id)
for move_line in aml:
base_lines = move_line.move_id.line_ids.filtered(lambda line: move_line.tax_line_id in line.tax_ids and move_line.product_id == line.product_id)
move_line.tax_base_amount = abs(sum(base_lines.mapped('balance')))
remaining_aml = self - aml
if remaining_aml:
return super(AccountMoveLine, remaining_aml)._compute_tax_base_amount()
class AccountTax(models.Model):
_inherit = 'account.tax'
l10n_in_reverse_charge = fields.Boolean("Reverse charge", help="Tick this if this tax is reverse charge. Only for Indian accounting")
@api.model
def _get_generation_dict_from_base_line(self, line_vals, tax_vals, force_caba_exigibility=False):
# EXTENDS account
# Group taxes also by product.
res = super()._get_generation_dict_from_base_line(line_vals, tax_vals, force_caba_exigibility=force_caba_exigibility)
record = line_vals['record']
if isinstance(record, models.Model)\
and record._name == 'account.move.line'\
and record.company_id.account_fiscal_country_id.code == 'IN':
res['product_id'] = record.product_id.id
res['product_uom_id'] = record.product_uom_id.id
return res
@api.model
def _get_generation_dict_from_tax_line(self, line_vals):
# EXTENDS account
# Group taxes also by product.
res = super()._get_generation_dict_from_tax_line(line_vals)
record = line_vals['record']
if isinstance(record, models.Model)\
and record._name == 'account.move.line'\
and record.company_id.account_fiscal_country_id.code == 'IN':
res['product_id'] = record.product_id.id
res['product_uom_id'] = record.product_uom_id.id
return res

View file

@ -0,0 +1,15 @@
from odoo import api, fields, models
class AccountAccount(models.Model):
_inherit = 'account.account'
l10n_in_tds_tcs_section_id = fields.Many2one('l10n_in.section.alert', string="TCS/TDS Section")
l10n_in_tds_feature_enabled = fields.Boolean(compute='_compute_tds_tcs_features', store=True)
l10n_in_tcs_feature_enabled = fields.Boolean(compute='_compute_tds_tcs_features', store=True)
@api.depends('company_ids.l10n_in_tds_feature', 'company_ids.l10n_in_tcs_feature')
def _compute_tds_tcs_features(self):
for record in self:
record.l10n_in_tds_feature_enabled = any(company.l10n_in_tds_feature for company in record.company_ids)
record.l10n_in_tcs_feature_enabled = any(company.l10n_in_tcs_feature for company in record.company_ids)

View file

@ -1,19 +1,34 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import logging
import json
import re
from odoo import api, fields, models, _
from contextlib import contextmanager
from markupsafe import Markup
from odoo import Command, _, api, fields, models
from odoo.exceptions import ValidationError, RedirectWarning, UserError
from odoo.tools.float_utils import json_float_round
from odoo.tools.image import image_data_uri
from odoo.tools import float_compare, SQL
from odoo.tools.date_utils import get_month
from odoo.addons.l10n_in.models.iap_account import IAP_SERVICE_NAME
EDI_CANCEL_REASON = {
# Same for both e-way bill and IRN
'1': "Duplicate",
'2': "Data Entry Mistake",
'3': "Order Cancelled",
'4': "Others",
}
_logger = logging.getLogger(__name__)
class AccountMove(models.Model):
_inherit = "account.move"
amount_total_words = fields.Char("Total (In Words)", compute="_compute_amount_total_words")
l10n_in_gst_treatment = fields.Selection([
l10n_in_gst_treatment = fields.Selection(
selection=[
('regular', 'Registered Business - Regular'),
('composition', 'Registered Business - Composition'),
('unregistered', 'Unregistered Business'),
@ -22,57 +37,517 @@ class AccountMove(models.Model):
('special_economic_zone', 'Special Economic Zone'),
('deemed_export', 'Deemed Export'),
('uin_holders', 'UIN Holders'),
], string="GST Treatment", compute="_compute_l10n_in_gst_treatment", store=True, readonly=False, copy=True)
l10n_in_state_id = fields.Many2one('res.country.state', string="Place of supply", compute="_compute_l10n_in_state_id", store=True, readonly=False)
],
string="GST Treatment",
compute="_compute_l10n_in_gst_treatment",
store=True,
readonly=False,
copy=True,
precompute=True
)
l10n_in_state_id = fields.Many2one(
comodel_name='res.country.state',
string="Place of supply",
compute="_compute_l10n_in_state_id",
store=True,
copy=True,
readonly=False,
precompute=True
)
l10n_in_gstin = fields.Char(string="GSTIN")
# For Export invoice this data is need in GSTR report
l10n_in_shipping_bill_number = fields.Char('Shipping bill number', readonly=True, states={'draft': [('readonly', False)]})
l10n_in_shipping_bill_date = fields.Date('Shipping bill date', readonly=True, states={'draft': [('readonly', False)]})
l10n_in_shipping_port_code_id = fields.Many2one('l10n_in.port.code', 'Port code', readonly=True, states={'draft': [('readonly', False)]})
l10n_in_reseller_partner_id = fields.Many2one('res.partner', 'Reseller', domain=[('vat', '!=', False)], help="Only Registered Reseller", readonly=True, states={'draft': [('readonly', False)]})
l10n_in_shipping_bill_number = fields.Char('Shipping bill number')
l10n_in_shipping_bill_date = fields.Date('Shipping bill date')
l10n_in_shipping_port_code_id = fields.Many2one('l10n_in.port.code', 'Port code')
l10n_in_reseller_partner_id = fields.Many2one(
comodel_name='res.partner',
string="Reseller",
domain=[('vat', '!=', False)],
help="Only Registered Reseller"
)
l10n_in_journal_type = fields.Selection(string="Journal Type", related='journal_id.type')
l10n_in_warning = fields.Json(compute="_compute_l10n_in_warning")
l10n_in_is_gst_registered_enabled = fields.Boolean(related='company_id.l10n_in_is_gst_registered')
l10n_in_tds_deduction = fields.Selection(related='commercial_partner_id.l10n_in_pan_entity_id.tds_deduction', string="TDS Deduction")
@api.depends('amount_total')
def _compute_amount_total_words(self):
for invoice in self:
invoice.amount_total_words = invoice.currency_id.amount_to_text(invoice.amount_total)
# withholding related fields
l10n_in_is_withholding = fields.Boolean(
string="Is Indian TDS Entry",
copy=False,
help="Technical field to identify Indian withholding entry"
)
l10n_in_withholding_ref_move_id = fields.Many2one(
comodel_name='account.move',
string="Indian TDS Ref Move",
readonly=True,
index='btree_not_null',
copy=False,
help="Reference move for withholding entry",
)
l10n_in_withholding_ref_payment_id = fields.Many2one(
comodel_name='account.payment',
string="Indian TDS Ref Payment",
index='btree_not_null',
readonly=True,
copy=False,
help="Reference Payment for withholding entry",
)
l10n_in_withhold_move_ids = fields.One2many(
'account.move', 'l10n_in_withholding_ref_move_id',
string="Indian TDS Entries"
)
l10n_in_withholding_line_ids = fields.One2many(
'account.move.line', 'move_id',
string="Indian TDS Lines",
compute='_compute_l10n_in_withholding_line_ids',
)
l10n_in_total_withholding_amount = fields.Monetary(
string="Total Indian TDS Amount",
compute='_compute_l10n_in_total_withholding_amount',
help="Total withholding amount for the move",
)
l10n_in_display_higher_tcs_button = fields.Boolean(string="Display higher TCS button", compute="_compute_l10n_in_display_higher_tcs_button")
l10n_in_tds_feature_enabled = fields.Boolean(related='company_id.l10n_in_tds_feature')
l10n_in_tcs_feature_enabled = fields.Boolean(related='company_id.l10n_in_tcs_feature')
# gstin_status related field
l10n_in_partner_gstin_status = fields.Boolean(
string="GST Status",
compute="_compute_l10n_in_partner_gstin_status_and_date",
)
l10n_in_show_gstin_status = fields.Boolean(compute="_compute_l10n_in_show_gstin_status")
l10n_in_gstin_verified_date = fields.Date(compute="_compute_l10n_in_partner_gstin_status_and_date")
# -------------------------------------------------------------------------
# COMPUTE METHODS
# -------------------------------------------------------------------------
@api.depends('partner_id')
def _compute_l10n_in_gst_treatment(self):
indian_invoice = self.filtered(lambda m: m.country_code == 'IN')
for record in indian_invoice:
gst_treatment = record.partner_id.l10n_in_gst_treatment
if not gst_treatment:
gst_treatment = 'unregistered'
if record.partner_id.country_id.code == 'IN' and record.partner_id.vat:
gst_treatment = 'regular'
elif record.partner_id.country_id and record.partner_id.country_id.code != 'IN':
gst_treatment = 'overseas'
record.l10n_in_gst_treatment = gst_treatment
(self - indian_invoice).l10n_in_gst_treatment = False
for invoice in self.filtered(lambda m: m.country_code == 'IN' and m.state == 'draft'):
partner = invoice.partner_id
invoice.l10n_in_gst_treatment = (
partner.l10n_in_gst_treatment
or (
'overseas' if partner.country_id and partner.country_id.code != 'IN'
else partner.check_vat_in(partner.vat) and 'regular' or 'consumer'
)
)
@api.depends('partner_id', 'company_id')
@api.depends('partner_id', 'partner_shipping_id', 'company_id')
def _compute_l10n_in_state_id(self):
foreign_state = self.env.ref('l10n_in.state_in_oc', raise_if_not_found=False)
for move in self:
if move.country_code == 'IN' and move.journal_id.type == 'sale':
country_code = move.partner_id.country_id.code
if country_code == 'IN':
move.l10n_in_state_id = move.partner_id.state_id
elif country_code:
move.l10n_in_state_id = self.env.ref('l10n_in.state_in_oc', raise_if_not_found=False)
else:
move.l10n_in_state_id = move.company_id.state_id
if move.country_code == 'IN' and move.is_sale_document(include_receipts=True):
partner = (
move.partner_id.commercial_partner_id == move.partner_shipping_id.commercial_partner_id
and move.partner_shipping_id
or move.partner_id
)
if partner.country_id and partner.country_id.code != 'IN':
move.l10n_in_state_id = foreign_state
continue
partner_state = partner.state_id or move.partner_id.commercial_partner_id.state_id or move.company_id.state_id
country_code = partner_state.country_id.code or move.country_code
move.l10n_in_state_id = partner_state if country_code == 'IN' else foreign_state
elif move.country_code == 'IN' and move.journal_id.type == 'purchase':
move.l10n_in_state_id = move.company_id.state_id
else:
move.l10n_in_state_id = False
@api.depends('l10n_in_state_id', 'l10n_in_gst_treatment')
def _compute_fiscal_position_id(self):
foreign_state = self.env['res.country.state'].search([('code', '!=', 'IN')], limit=1)
def _get_fiscal_state(move):
"""
Maps each move to its corresponding fiscal state based on its type,
fiscal conditions, and the state of the associated partner or company.
"""
if (
move.country_code != 'IN'
or not move.is_invoice(include_receipts=True)
# Partner's FP takes precedence through super
or move.partner_shipping_id.property_account_position_id
or move.partner_id.property_account_position_id
):
return False
elif move.l10n_in_gst_treatment == 'special_economic_zone':
# Special Economic Zone
return self.env.ref('l10n_in.state_in_oc')
elif move.is_sale_document(include_receipts=True):
# In Sales Documents: Compare place of supply with company state
return move.l10n_in_state_id if move.l10n_in_state_id.l10n_in_tin != '96' else foreign_state
elif move.is_purchase_document(include_receipts=True) and move.partner_id.country_id.code == 'IN':
# In Purchases Documents: Compare place of supply with vendor state
pos_state_id = move.l10n_in_state_id
if pos_state_id.l10n_in_tin == '96':
return pos_state_id
elif pos_state_id == move.partner_id.state_id:
# Intra-State: Group by state matching the company's state.
return move.company_id.state_id
elif pos_state_id != move.partner_id.state_id:
# Inter-State: Group by state that doesn't match the company's state.
return (
pos_state_id == move.company_id.state_id
and move.partner_id.state_id
or pos_state_id
)
return False
FiscalPosition = self.env['account.fiscal.position']
for state_id, moves in self.grouped(_get_fiscal_state).items():
if state_id:
virtual_partner = self.env['res.partner'].new({
'state_id': state_id.id,
'country_id': state_id.country_id.id,
})
# Group moves by company to avoid multi-company conflicts
for company_id, company_moves in moves.grouped('company_id').items():
company_moves.fiscal_position_id = FiscalPosition.with_company(
company_id
)._get_fiscal_position(virtual_partner)
else:
super(AccountMove, moves)._compute_fiscal_position_id()
@api.onchange('name')
def _onchange_name_warning(self):
if (
self.country_code == 'IN'
and self.company_id.l10n_in_is_gst_registered
and self.journal_id.type == 'sale'
and self.name
and (len(self.name) > 16 or not re.match(r'^[a-zA-Z0-9-\/]+$', self.name))
):
return {'warning': {
'title' : _("Invalid sequence as per GST rule 46(b)"),
'message': _(
"The invoice number should not exceed 16 characters\n"
"and must only contain '-' (hyphen) and '/' (slash) as special characters"
)
}}
return super()._onchange_name_warning()
@api.depends(
'invoice_line_ids.l10n_in_hsn_code',
'company_id.l10n_in_hsn_code_digit',
'invoice_line_ids.tax_ids',
'commercial_partner_id.l10n_in_pan_entity_id',
'invoice_line_ids.price_total'
)
def _compute_l10n_in_warning(self):
indian_invoice = self.filtered(lambda m: m.country_code == 'IN' and m.move_type != 'entry')
line_filter_func = lambda line: line.display_type == 'product' and line.tax_ids and line._origin
_xmlid_to_res_id = self.env['ir.model.data']._xmlid_to_res_id
for move in indian_invoice:
warnings = {}
company = move.company_id
action_name = _("Journal Item(s)")
action_text = _("View Journal Item(s)")
if company.l10n_in_tcs_feature or company.l10n_in_tds_feature:
invalid_tax_lines = move._get_l10n_in_invalid_tax_lines()
if company.l10n_in_tcs_feature and invalid_tax_lines:
warnings['lower_tcs_tax'] = {
'message': _("As the Partner's PAN missing/invalid apply TCS at the higher rate."),
'actions': invalid_tax_lines.with_context(tax_validation=True)._get_records_action(
name=action_name,
views=[(_xmlid_to_res_id("l10n_in.view_move_line_tree_hsn_l10n_in"), "list")],
domain=[('id', 'in', invalid_tax_lines.ids)],
),
'action_text': action_text,
}
if applicable_sections := move._get_l10n_in_tds_tcs_applicable_sections():
tds_tcs_applicable_lines = (
move.move_type == 'out_invoice'
and move._get_tcs_applicable_lines(move.invoice_line_ids)
or move.invoice_line_ids
)
warnings['tds_tcs_threshold_alert'] = {
'message': applicable_sections._get_warning_message(),
'action': tds_tcs_applicable_lines.with_context(
default_tax_type_use=True,
move_type=move.move_type == 'in_invoice'
)._get_records_action(
name=action_name,
domain=[('id', 'in', tds_tcs_applicable_lines.ids)],
views=[(_xmlid_to_res_id("l10n_in.view_move_line_list_l10n_in_withholding"), "list")]
),
'action_text': action_text,
}
if (
company.l10n_in_is_gst_registered
and company.l10n_in_hsn_code_digit
and (filtered_lines := move.invoice_line_ids.filtered(line_filter_func))
):
lines = self.env['account.move.line']
for line in filtered_lines:
hsn_code = line.l10n_in_hsn_code
if (
not hsn_code
or (
not re.match(r'^\d{4}$|^\d{6}$|^\d{8}$', hsn_code)
or len(hsn_code) < int(company.l10n_in_hsn_code_digit)
)
):
lines |= line._origin
if lines:
digit_suffixes = {
'4': _("4 digits, 6 digits or 8 digits"),
'6': _("6 digits or 8 digits"),
'8': _("8 digits")
}
msg = _(
"Ensure that the HSN/SAC Code consists either %s in invoice lines",
digit_suffixes.get(company.l10n_in_hsn_code_digit, _("Invalid HSN/SAC Code digit"))
)
warnings['invalid_hsn_code_length'] = {
'message': msg,
'action': lines._get_records_action(
name=action_name,
views=[(_xmlid_to_res_id("l10n_in.view_move_line_tree_hsn_l10n_in"), "list")],
domain=[('id', 'in', lines.ids)]
),
'action_text': action_text,
}
move.l10n_in_warning = warnings
(self - indian_invoice).l10n_in_warning = {}
@api.depends('partner_id', 'state', 'payment_state', 'l10n_in_gst_treatment')
def _compute_l10n_in_show_gstin_status(self):
indian_moves = self.filtered(
lambda m: m.country_code == 'IN' and m.company_id.l10n_in_gstin_status_feature
)
(self - indian_moves).l10n_in_show_gstin_status = False
for move in indian_moves:
move.l10n_in_show_gstin_status = (
move.partner_id
and move.state == 'posted'
and move.move_type != 'entry'
and move.payment_state not in ['paid', 'reversed']
and move.l10n_in_gst_treatment in [
'regular',
'composition',
'special_economic_zone',
'deemed_export',
'uin_holders'
]
)
@api.depends('partner_id')
def _compute_l10n_in_partner_gstin_status_and_date(self):
for move in self:
if (
move.country_code == 'IN'
and move.company_id.l10n_in_gstin_status_feature
and move.payment_state not in ['paid', 'reversed']
and move.state != 'cancel'
):
move.l10n_in_partner_gstin_status = move.partner_id.l10n_in_gstin_verified_status
move.l10n_in_gstin_verified_date = move.partner_id.l10n_in_gstin_verified_date
else:
move.l10n_in_partner_gstin_status = False
move.l10n_in_gstin_verified_date = False
@api.depends('line_ids', 'l10n_in_is_withholding')
def _compute_l10n_in_withholding_line_ids(self):
# Compute the withholding lines for the move
for move in self:
if move.l10n_in_is_withholding:
move.l10n_in_withholding_line_ids = move.line_ids.filtered('tax_ids')
else:
move.l10n_in_withholding_line_ids = False
def _compute_l10n_in_total_withholding_amount(self):
for move in self:
if self.env.company.l10n_in_tds_feature:
move.l10n_in_total_withholding_amount = sum(
move.l10n_in_withhold_move_ids.filtered(
lambda m: m.state == 'posted'
).l10n_in_withholding_line_ids.mapped('l10n_in_withhold_tax_amount')
)
else:
move.l10n_in_total_withholding_amount = 0.0
@api.depends('l10n_in_warning')
def _compute_l10n_in_display_higher_tcs_button(self):
for move in self:
if move.company_id.l10n_in_tcs_feature:
move.l10n_in_display_higher_tcs_button = (
move.l10n_in_warning
and move.l10n_in_warning.get('lower_tcs_tax')
)
else:
move.l10n_in_display_higher_tcs_button = False
def action_l10n_in_withholding_entries(self):
self.ensure_one()
return {
'name': "TDS Entries",
'type': 'ir.actions.act_window',
'res_model': 'account.move',
'view_mode': 'list,form',
'domain': [('id', 'in', self.l10n_in_withhold_move_ids.ids)],
}
def action_l10n_in_apply_higher_tax(self):
self.ensure_one()
invalid_lines = self._get_l10n_in_invalid_tax_lines()
for line in invalid_lines:
updated_tax_ids = []
for tax in line.tax_ids:
if tax.l10n_in_tax_type == 'tcs':
max_tax = max(
tax.l10n_in_section_id.l10n_in_section_tax_ids,
key=lambda t: t.amount
)
updated_tax_ids.append(max_tax.id)
else:
updated_tax_ids.append(tax.id)
if set(line.tax_ids.ids) != set(updated_tax_ids):
line.write({'tax_ids': [Command.clear()] + [Command.set(updated_tax_ids)]})
def _get_l10n_in_invalid_tax_lines(self):
self.ensure_one()
if self.country_code == 'IN' and not self.commercial_partner_id.l10n_in_pan_entity_id:
lines = self.env['account.move.line']
for line in self.invoice_line_ids:
for tax in line.tax_ids:
if (
tax.l10n_in_tax_type == 'tcs'
and tax.amount != max(tax.l10n_in_section_id.l10n_in_section_tax_ids, key=lambda t: abs(t.amount)).amount
):
lines |= line._origin
return lines
def _get_sections_aggregate_sum_by_pan(self, section_alert, commercial_partner_id):
self.ensure_one()
month_start_date, month_end_date = get_month(self.date)
company_fiscalyear_dates = self.company_id.sudo().compute_fiscalyear_dates(self.date)
fiscalyear_start_date, fiscalyear_end_date = company_fiscalyear_dates['date_from'], company_fiscalyear_dates['date_to']
default_domain = [
('account_id.l10n_in_tds_tcs_section_id', '=', section_alert.id),
('move_id.move_type', '!=', 'entry'),
('company_id.l10n_in_tds_feature', '!=', False),
('company_id.l10n_in_tan', '=', self.company_id.l10n_in_tan),
('parent_state', '=', 'posted')
]
if commercial_partner_id.l10n_in_pan_entity_id:
default_domain += [('move_id.commercial_partner_id.l10n_in_pan_entity_id', '=', commercial_partner_id.l10n_in_pan_entity_id.id)]
else:
default_domain += [('move_id.commercial_partner_id', '=', commercial_partner_id.id)]
frequency_domains = {
'monthly': [('date', '>=', month_start_date), ('date', '<=', month_end_date)],
'fiscal_yearly': [('date', '>=', fiscalyear_start_date), ('date', '<=', fiscalyear_end_date)],
}
aggregate_result = {}
for frequency, frequency_domain in frequency_domains.items():
query = self.env['account.move.line']._search(default_domain + frequency_domain, bypass_access=True, active_test=False)
result = self.env.execute_query_dict(SQL(
"""
SELECT COALESCE(sum(account_move_line.balance), 0) as balance,
COALESCE(sum(account_move_line.price_total * am.invoice_currency_rate), 0) as price_total
FROM %s
JOIN account_move AS am ON am.id = account_move_line.move_id
WHERE %s
""",
query.from_clause,
query.where_clause)
)
aggregate_result[frequency] = result[0]
return aggregate_result
def _l10n_in_is_warning_applicable(self, section_id):
self.ensure_one()
match section_id.tax_source_type:
case 'tcs':
return self.company_id.l10n_in_tcs_feature and self.journal_id.type == 'sale'
case 'tds':
return (
self.company_id.l10n_in_tds_feature
and self.journal_id.type == 'purchase'
and section_id not in self.l10n_in_withhold_move_ids.filtered(lambda m:
m.state == 'posted'
).mapped('line_ids.tax_ids.l10n_in_section_id')
)
case _:
return False
def _get_l10n_in_tds_tcs_applicable_sections(self):
def _group_by_section_alert(invoice_lines):
group_by_lines = {}
for line in invoice_lines:
group_key = line.account_id.sudo().l10n_in_tds_tcs_section_id
if group_key and not line.company_currency_id.is_zero(line.price_total):
group_by_lines.setdefault(group_key, [])
group_by_lines[group_key].append(line)
return group_by_lines
def _is_section_applicable(section_alert, threshold_sums, invoice_currency_rate, lines):
lines_total = sum(
(line.price_total * invoice_currency_rate) if section_alert.consider_amount == 'total_amount' else line.balance
for line in lines
)
if section_alert.is_aggregate_limit:
aggregate_period_key = section_alert.consider_amount == 'total_amount' and 'price_total' or 'balance'
aggregate_total = threshold_sums.get(section_alert.aggregate_period, {}).get(aggregate_period_key)
if self.state == 'draft':
aggregate_total += lines_total
if aggregate_total > section_alert.aggregate_limit:
return True
return (
section_alert.is_per_transaction_limit
and lines_total > section_alert.per_transaction_limit
)
if self.country_code == 'IN' and self.move_type in ['in_invoice', 'out_invoice']:
warning = set()
commercial_partner_id = self.commercial_partner_id
if commercial_partner_id.l10n_in_pan_entity_id.tds_deduction == 'no':
invoice_lines = self.invoice_line_ids.filtered(lambda l: l.account_id.l10n_in_tds_tcs_section_id.tax_source_type != 'tds')
else:
invoice_lines = self.invoice_line_ids
existing_section = (
self.l10n_in_withhold_move_ids.line_ids + self.line_ids
).tax_ids.l10n_in_section_id
for section_alert, lines in _group_by_section_alert(invoice_lines).items():
if (
(section_alert not in existing_section
or self._get_tcs_applicable_lines(lines))
and self._l10n_in_is_warning_applicable(section_alert)
and _is_section_applicable(
section_alert,
self._get_sections_aggregate_sum_by_pan(
section_alert,
commercial_partner_id
),
self.invoice_currency_rate,
lines
)
):
warning.add(section_alert.id)
return self.env['l10n_in.section.alert'].browse(warning)
def _get_tcs_applicable_lines(self, lines):
tcs_applicable_lines = set()
for line in lines:
if line.l10n_in_tds_tcs_section_id not in line.tax_ids.l10n_in_section_id:
tcs_applicable_lines.add(line.id)
return self.env['account.move.line'].browse(tcs_applicable_lines)
def l10n_in_verify_partner_gstin_status(self):
self.ensure_one()
return self.with_company(self.company_id).partner_id.action_l10n_in_verify_gstin_status()
def _get_name_invoice_report(self):
self.ensure_one()
if self.country_code == 'IN':
# TODO: remove the view mode check in master, only for stable releases
in_invoice_view = self.env.ref('l10n_in.l10n_in_report_invoice_document_inherit', raise_if_not_found=False)
if (in_invoice_view and in_invoice_view.sudo().mode == "primary"):
return 'l10n_in.l10n_in_report_invoice_document_inherit'
return 'l10n_in.l10n_in_report_invoice_document_inherit'
return super()._get_name_invoice_report()
def _post(self, soft=True):
@ -80,14 +555,12 @@ class AccountMove(models.Model):
posted = super()._post(soft)
gst_treatment_name_mapping = {k: v for k, v in
self._fields['l10n_in_gst_treatment']._description_selection(self.env)}
for move in posted.filtered(lambda m: m.country_code == 'IN' and m.is_sale_document()):
"""Check state is set in company/sub-unit"""
company_unit_partner = move.journal_id.l10n_in_gstin_partner_id or move.journal_id.company_id
for move in posted.filtered(lambda m: m.country_code == 'IN' and m.company_id.l10n_in_is_gst_registered and m.is_sale_document()):
if move.l10n_in_state_id and not move.l10n_in_state_id.l10n_in_tin:
raise UserError(_("Please set a valid TIN Number on the Place of Supply %s", move.l10n_in_state_id.name))
if not company_unit_partner.state_id:
if not move.company_id.state_id:
msg = _("Your company %s needs to have a correct address in order to validate this invoice.\n"
"Set the address of your company (Don't forget the State field)") % (company_unit_partner.name)
"Set the address of your company (Don't forget the State field)", move.company_id.name)
action = {
"view_mode": "form",
"res_model": "res.company",
@ -96,9 +569,16 @@ class AccountMove(models.Model):
"views": [[self.env.ref("base.view_company_form").id, "form"]],
}
raise RedirectWarning(msg, action, _('Go to Company configuration'))
move.l10n_in_gstin = move.partner_id.vat
if not move.l10n_in_gstin and move.l10n_in_gst_treatment in ['regular', 'composition', 'special_economic_zone', 'deemed_export']:
if (
not move.l10n_in_gstin
and move.l10n_in_gst_treatment in [
'regular',
'composition',
'special_economic_zone',
'deemed_export'
]
):
raise ValidationError(_(
"Partner %(partner_name)s (%(partner_id)s) GSTIN is required under GST Treatment %(name)s",
partner_name=move.partner_id.name,
@ -113,30 +593,163 @@ class AccountMove(models.Model):
self.ensure_one()
return False
@api.ondelete(at_uninstall=False)
def _unlink_l10n_in_except_once_post(self):
# Prevent deleting entries once it's posted for Indian Company only
if any(m.country_code == 'IN' and m.posted_before for m in self) and not self._context.get('force_delete'):
raise UserError(_("To keep the audit trail, you can not delete journal entries once they have been posted.\nInstead, you can cancel the journal entry."))
def _can_be_unlinked(self):
self.ensure_one()
return (self.country_code != 'IN' or not self.posted_before) and super()._can_be_unlinked()
def unlink(self):
# Add logger here becouse in api ondelete account.move.line is deleted and we can't get total amount
logger_msg = False
if any(m.country_code == 'IN' and m.posted_before for m in self):
if self._context.get('force_delete'):
moves_details = ", ".join("{entry_number} ({move_id}) amount {amount_total} {currency} and partner {partner_name}".format(
entry_number=m.name,
move_id=m.id,
amount_total=m.amount_total,
currency=m.currency_id.name,
partner_name=m.partner_id.display_name)
for m in self)
logger_msg = 'Force deleted Journal Entries %s by %s (%s)' % (moves_details, self.env.user.name, self.env.user.id)
res = super().unlink()
if logger_msg:
_logger.info(logger_msg)
return res
def _generate_qr_code(self, silent_errors=False):
self.ensure_one()
if self.company_id.country_code == 'IN' and self.company_id.l10n_in_upi_id:
payment_url = 'upi://pay?pa=%s&pn=%s&am=%s&tr=%s&tn=%s' % (
self.company_id.l10n_in_upi_id,
self.company_id.name,
self.amount_residual,
self.payment_reference or self.name,
("Payment for %s" % self.name))
barcode = self.env['ir.actions.report'].barcode(barcode_type="QR", value=payment_url, width=120, height=120, quiet=False)
return image_data_uri(base64.b64encode(barcode))
return super()._generate_qr_code(silent_errors)
def _l10n_in_get_hsn_summary_table(self):
self.ensure_one()
base_lines, _tax_lines = self._get_rounded_base_and_tax_lines()
display_uom = self.env.user.has_group('uom.group_uom')
return self.env['account.tax']._l10n_in_get_hsn_summary_table(base_lines, display_uom)
def _l10n_in_get_bill_from_irn(self, irn):
# TO OVERRIDE
return False
# ------Utils------
@api.model
def _l10n_in_prepare_tax_details(self):
def l10n_in_grouping_key_generator(base_line, tax_data):
invl = base_line['record']
tax = tax_data['tax']
if self.l10n_in_gst_treatment in ('overseas', 'special_economic_zone') and all(
self.env.ref("l10n_in.tax_tag_igst") in rl.tag_ids
for rl in tax.invoice_repartition_line_ids if rl.repartition_type == 'tax'
):
tax_data['is_reverse_charge'] = False
tag_ids = tax.invoice_repartition_line_ids.tag_ids.ids
line_code = "other"
xmlid_to_res_id = self.env['ir.model.data']._xmlid_to_res_id
if not invl.currency_id.is_zero(tax_data['tax_amount_currency']):
if xmlid_to_res_id("l10n_in.tax_tag_cess") in tag_ids:
if tax.amount_type != "percent":
line_code = "cess_non_advol"
else:
line_code = "cess"
elif xmlid_to_res_id("l10n_in.tax_tag_state_cess") in tag_ids:
if tax.amount_type != "percent":
line_code = "state_cess_non_advol"
else:
line_code = "state_cess"
else:
for gst in ["cgst", "sgst", "igst"]:
if xmlid_to_res_id("l10n_in.tax_tag_%s" % (gst)) in tag_ids:
# need to separate rc tax value so it's not pass to other values
line_code = f'{gst}_rc' if tax_data['is_reverse_charge'] else gst
return {
"tax": tax,
"base_product_id": invl.product_id,
"tax_product_id": invl.product_id,
"base_product_uom_id": invl.product_uom_id,
"tax_product_uom_id": invl.product_uom_id,
"line_code": line_code,
}
def l10n_in_filter_to_apply(base_line, tax_values):
return base_line['record'].display_type != 'rounding'
return self._prepare_invoice_aggregated_taxes(
filter_tax_values_to_apply=l10n_in_filter_to_apply,
grouping_key_generator=l10n_in_grouping_key_generator,
)
def _get_l10n_in_seller_buyer_party(self):
self.ensure_one()
return {
"seller_details": self.company_id.partner_id,
"dispatch_details": self._l10n_in_get_warehouse_address() or self.company_id.partner_id,
"buyer_details": self.partner_id,
"ship_to_details": self.partner_shipping_id or self.partner_id
}
@api.model
def _l10n_in_extract_digits(self, string):
if not string:
return ""
matches = re.findall(r"\d+", string)
return "".join(matches)
@api.model
def _l10n_in_is_service_hsn(self, hsn_code):
return self._l10n_in_extract_digits(hsn_code).startswith('99')
@api.model
def _l10n_in_round_value(self, amount, precision_digits=2):
"""
This method is call for rounding.
If anything is wrong with rounding then we quick fix in method
"""
return json_float_round(amount, precision_digits)
@api.model
def _get_l10n_in_tax_details_by_line_code(self, tax_details):
l10n_in_tax_details = {}
for tax_detail in tax_details.values():
if tax_detail["tax"].l10n_in_reverse_charge:
l10n_in_tax_details.setdefault("is_reverse_charge", True)
line_code = tax_detail["line_code"]
l10n_in_tax_details.setdefault("%s_rate" % (line_code), tax_detail["tax"].amount)
l10n_in_tax_details.setdefault("%s_amount" % (line_code), 0.00)
l10n_in_tax_details.setdefault("%s_amount_currency" % (line_code), 0.00)
l10n_in_tax_details["%s_amount" % (line_code)] += tax_detail["tax_amount"]
l10n_in_tax_details["%s_amount_currency" % (line_code)] += tax_detail["tax_amount_currency"]
return l10n_in_tax_details
@api.model
def _l10n_in_edi_get_iap_buy_credits_message(self):
url = self.env['iap.account'].get_credits_url(service_name=IAP_SERVICE_NAME)
return Markup("""<p><b>%s</b></p><p>%s <a href="%s">%s</a></p>""") % (
_("You have insufficient credits to send this document!"),
_("Please buy more credits and retry: "),
url,
_("Buy Credits")
)
def _get_sync_stack(self, container):
stack, update_containers = super()._get_sync_stack(container)
if all(move.country_code != 'IN' for move in self):
return stack, update_containers
_tax_container, invoice_container, misc_container = update_containers()
moves = invoice_container['records'] + misc_container['records']
stack.append((9, self._sync_l10n_in_gstr_section(moves)))
return stack, update_containers
@contextmanager
def _sync_l10n_in_gstr_section(self, moves):
yield
tax_tags_dict = self.env['account.move.line']._get_l10n_in_tax_tag_ids()
# we set the section on the invoice lines
moves.line_ids._set_l10n_in_gstr_section(tax_tags_dict)
def _get_l10n_in_invoice_label(self):
self.ensure_one()
exempt_types = {'exempt', 'nil_rated', 'non_gst'}
if self.country_code != 'IN' or not self.is_sale_document(include_receipts=False):
return
gst_treatment = self.l10n_in_gst_treatment
company = self.company_id
tax_types = set(self.invoice_line_ids.tax_ids.mapped('l10n_in_tax_type'))
if company.l10n_in_is_gst_registered and tax_types:
if gst_treatment in ['overseas', 'special_economic_zone']:
return 'Tax Invoice'
elif tax_types.issubset(exempt_types):
return 'Bill of Supply'
elif tax_types.isdisjoint(exempt_types):
return 'Tax Invoice'
elif gst_treatment in ['unregistered', 'consumer']:
return 'Invoice-cum-Bill of Supply'
return 'Invoice'

View file

@ -0,0 +1,30 @@
from odoo import models
class AccountJournal(models.Model):
_inherit = "account.journal"
def _update_payment_method_lines(self, payment_type):
bank_journals = self.filtered(lambda j: j.type == "bank" and j.company_id.chart_template == "in")
if not bank_journals:
return
if payment_type == 'inbound':
account_xmlid = "account_journal_payment_debit_account_id"
else:
account_xmlid = "account_journal_payment_credit_account_id"
lines_to_update = bank_journals[f"{payment_type}_payment_method_line_ids"].filtered(
lambda l: l.payment_method_id.code == 'manual'
)
for company, lines in lines_to_update.grouped('company_id').items():
if account := self.env['account.chart.template'].with_company(company).ref(account_xmlid, raise_if_not_found=False):
lines.payment_account_id = account
def _compute_inbound_payment_method_line_ids(self):
super()._compute_inbound_payment_method_line_ids()
self._update_payment_method_lines("inbound")
def _compute_outbound_payment_method_line_ids(self):
super()._compute_outbound_payment_method_line_ids()
self._update_payment_method_lines("outbound")

View file

@ -0,0 +1,318 @@
import re
from datetime import date
from odoo import _, api, fields, models
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
l10n_in_hsn_code = fields.Char(string="HSN/SAC Code", compute="_compute_l10n_in_hsn_code", store=True, readonly=False, copy=False)
l10n_in_gstr_section = fields.Selection(
selection=[
("sale_b2b_rcm", "B2B RCM"),
("sale_b2b_regular", "B2B Regular"),
("sale_b2cl", "B2CL"),
("sale_b2cs", "B2CS"),
("sale_exp_wp", "EXP(WP)"),
("sale_exp_wop", "EXP(WOP)"),
("sale_sez_wp", "SEZ(WP)"),
("sale_sez_wop", "SEZ(WOP)"),
("sale_deemed_export", "Deemed Export"),
("sale_cdnr_rcm", "CDNR RCM"),
("sale_cdnr_regular", "CDNR Regular"),
("sale_cdnr_deemed_export", "CDNR(Deemed Export)"),
("sale_cdnr_sez_wp", "CDNR(SEZ-WP)"),
("sale_cdnr_sez_wop", "CDNR(SEZ-WOP)"),
("sale_cdnur_b2cl", "CDNUR(B2CL)"),
("sale_cdnur_exp_wp", "CDNUR(EXP-WP)"),
("sale_cdnur_exp_wop", "CDNUR(EXP-WOP)"),
("sale_nil_rated", "Nil Rated"),
("sale_exempt", "Exempt"),
("sale_non_gst_supplies", "Non-GST Supplies"),
("sale_eco_9_5", "ECO 9(5)"),
("sale_out_of_scope", "Out of Scope"),
("purchase_b2b_regular", "B2B Regular"),
("purchase_b2c_regular", "B2C Regular"), # will be removed in master
("purchase_b2b_rcm", "B2B RCM"),
("purchase_b2c_rcm", "B2C RCM"),
("purchase_imp_services", "IMP(service)"),
("purchase_imp_goods", "IMP(goods)"),
("purchase_cdnr_regular", "CDNR Regular"),
("purchase_cdnur_regular", "CDNUR Regular"),
("purchase_cdnr_rcm", "CDNR RCM"),
("purchase_cdnur_rcm", "CDNUR RCM"),
("purchase_nil_rated", "Nil Rated"),
("purchase_exempt", "Exempt"),
("purchase_non_gst_supplies", "Non-GST Supplies"),
("purchase_out_of_scope", "Out of Scope"),
],
string="GSTR Section",
index="btree_not_null",
)
# withholding related fields
l10n_in_withhold_tax_amount = fields.Monetary(string="TDS Tax Amount", compute='_compute_l10n_in_withhold_tax_amount')
l10n_in_tds_tcs_section_id = fields.Many2one(related="account_id.l10n_in_tds_tcs_section_id")
@api.depends('tax_ids')
def _compute_l10n_in_withhold_tax_amount(self):
# Compute the withhold tax amount for the withholding lines
withholding_lines = self.filtered('move_id.l10n_in_is_withholding')
(self - withholding_lines).l10n_in_withhold_tax_amount = False
for line in withholding_lines:
line.l10n_in_withhold_tax_amount = line.currency_id.round(abs(line.price_total - line.price_subtotal))
@api.depends('product_id', 'product_id.l10n_in_hsn_code')
def _compute_l10n_in_hsn_code(self):
for line in self:
if line.move_id.country_code == 'IN' and line.parent_state == 'draft':
line.l10n_in_hsn_code = line.product_id.l10n_in_hsn_code
def _l10n_in_check_invalid_hsn_code(self):
self.ensure_one()
hsn_code = self.env['account.move']._l10n_in_extract_digits(self.l10n_in_hsn_code)
if not hsn_code:
return _("HSN code is not set in product line %(name)s", name=self.name)
elif not re.match(r'^\d{4}$|^\d{6}$|^\d{8}$', hsn_code):
return _(
"Invalid HSN Code (%(hsn_code)s) in product line %(product_line)s",
hsn_code=hsn_code,
product_line=self.product_id.name or self.name
)
return False
def _get_l10n_in_tax_tag_ids(self):
xmlid_to_res_id = self.env['ir.model.data']._xmlid_to_res_id
tag_refs = {
'sgst': ['l10n_in.tax_tag_base_sgst', 'l10n_in.tax_tag_sgst'],
'cgst': ['l10n_in.tax_tag_base_cgst', 'l10n_in.tax_tag_cgst'],
'igst': ['l10n_in.tax_tag_base_igst', 'l10n_in.tax_tag_igst'],
'cess': ['l10n_in.tax_tag_base_cess', 'l10n_in.tax_tag_cess'],
'eco_9_5': ['l10n_in.tax_tag_eco_9_5'],
}
return {
categ: [xmlid_to_res_id(xml_id) for xml_id in ref]
for categ, ref in tag_refs.items()
}
def _get_l10n_in_gstr_section(self, tax_tags_dict):
def tags_have_categ(line_tax_tags, categories):
return any(tag in line_tax_tags for category in categories for tag in tax_tags_dict.get(category, []))
def is_invoice(move):
return move.is_inbound() and not move.debit_origin_id
def is_move_bill(move):
return move.is_outbound() and not move.debit_origin_id
def get_transaction_type(move):
return 'intra_state' if move.l10n_in_state_id == move.company_id.state_id else 'inter_state'
def is_reverse_charge_tax(line):
return any(tax.l10n_in_reverse_charge for tax in line.tax_ids | line.tax_line_id)
def is_lut_tax(line):
return any(tax.l10n_in_is_lut for tax in line.tax_ids | line.tax_line_id)
def get_sales_section(line):
move = line.move_id
gst_treatment = move.l10n_in_gst_treatment
transaction_type = get_transaction_type(move)
line_tags = line.tax_tag_ids.ids
is_inv = is_invoice(move)
amt_limit = 100000 if not line.invoice_date or line.invoice_date >= date(2024, 11, 1) else 250000
# ECO 9(5) Section: Check if the line has the ECO 9(5) tax tag
if tags_have_categ(line_tags, ['eco_9_5']):
return 'sale_eco_9_5'
# Nil rated, Exempt, Non-GST Sales
if gst_treatment != 'overseas':
if any(tax.l10n_in_tax_type == 'nil_rated' for tax in line.tax_ids):
return 'sale_nil_rated'
elif any(tax.l10n_in_tax_type == 'exempt' for tax in line.tax_ids):
return 'sale_exempt'
elif any(tax.l10n_in_tax_type == 'non_gst' for tax in line.tax_ids):
return 'sale_non_gst_supplies'
# B2CS: Unregistered or Consumer sales with gst tags
if gst_treatment in ('unregistered', 'consumer') and not is_reverse_charge_tax(line):
if (transaction_type == 'intra_state' and tags_have_categ(line_tags, ['sgst', 'cgst', 'cess'])) or (
transaction_type == "inter_state"
and tags_have_categ(line_tags, ['igst', 'cess'])
and not is_lut_tax(line)
and (
is_inv and move.amount_total <= amt_limit
or move.debit_origin_id and move.debit_origin_id.amount_total <= amt_limit
or move.reversed_entry_id and move.reversed_entry_id.amount_total <= amt_limit
)
):
return 'sale_b2cs'
# If no relevant tags are found, or the tags do not match any category, mark as out of scope
if not line_tags or not tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess', 'eco_9_5']):
return 'sale_out_of_scope'
# If it's a standard invoice (not a debit/credit note)
if is_inv:
# B2B with Reverse Charge and Regular
if gst_treatment in ('regular', 'composition', 'uin_holders') and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess']) and not is_lut_tax(line):
if is_reverse_charge_tax(line):
return 'sale_b2b_rcm'
return 'sale_b2b_regular'
if not is_reverse_charge_tax(line):
# B2CL: Unregistered interstate sales above threshold
if (
gst_treatment in ('unregistered', 'consumer')
and tags_have_categ(line_tags, ['igst', 'cess'])
and not is_lut_tax(line)
and transaction_type == 'inter_state'
and move.amount_total > amt_limit
):
return 'sale_b2cl'
# Export with payment and without payment (under LUT) of tax
if gst_treatment == 'overseas' and tags_have_categ(line_tags, ['igst', 'cess']):
if is_lut_tax(line):
return 'sale_exp_wop'
return 'sale_exp_wp'
# SEZ with payment and without payment of tax
if gst_treatment == 'special_economic_zone' and tags_have_categ(line_tags, ['igst', 'cess']):
if is_lut_tax(line):
return 'sale_sez_wop'
return 'sale_sez_wp'
# Deemed export
if gst_treatment == 'deemed_export' and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess']) and not is_lut_tax(line):
return 'sale_deemed_export'
# If it's not a standard invoice (i.e., it's a debit/credit note)
if not is_inv:
# CDN for B2B reverse charge and B2B regular
if gst_treatment in ('regular', 'composition', 'uin_holders') and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess']) and not is_lut_tax(line):
if is_reverse_charge_tax(line):
return 'sale_cdnr_rcm'
return 'sale_cdnr_regular'
if not is_reverse_charge_tax(line):
# CDN for SEZ exports with payment and without payment
if gst_treatment == 'special_economic_zone' and tags_have_categ(line_tags, ['igst', 'cess']):
if is_lut_tax(line):
return 'sale_cdnr_sez_wop'
return 'sale_cdnr_sez_wp'
# CDN for deemed exports
if gst_treatment == 'deemed_export' and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess']) and not is_lut_tax(line):
return 'sale_cdnr_deemed_export'
# CDN for B2CL (interstate > threshold)
if (
gst_treatment in ('unregistered', 'consumer')
and tags_have_categ(line_tags, ['igst', 'cess'])
and not is_lut_tax(line)
and transaction_type == 'inter_state'
and (
move.debit_origin_id and move.debit_origin_id.amount_total > amt_limit
or move.reversed_entry_id and move.reversed_entry_id.amount_total > amt_limit
or not move.reversed_entry_id and not move.is_inbound()
)
):
return 'sale_cdnur_b2cl'
# CDN for exports with payment and without payment
if gst_treatment == 'overseas' and tags_have_categ(line_tags, ['igst', 'cess']):
if is_lut_tax(line):
return 'sale_cdnur_exp_wop'
return 'sale_cdnur_exp_wp'
# If none of the above match, default to out of scope
return 'sale_out_of_scope'
def get_purchase_section(line):
move = line.move_id
gst_treatment = move.l10n_in_gst_treatment
line_tags = line.tax_tag_ids.ids
is_bill = is_move_bill(move)
# Nil rated, Exempt, Non-GST purchases
if gst_treatment != 'overseas':
if any(tax.l10n_in_tax_type == 'nil_rated' for tax in line.tax_ids):
return 'purchase_nil_rated'
elif any(tax.l10n_in_tax_type == 'exempt' for tax in line.tax_ids):
return 'purchase_exempt'
elif any(tax.l10n_in_tax_type == 'non_gst' for tax in line.tax_ids):
return 'purchase_non_gst_supplies'
# If no relevant tags are found, or the tags do not match any category, mark as out of scope
if not line_tags or not tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess']):
return 'purchase_out_of_scope'
if is_bill:
# B2B Regular and Reverse Charge purchases
if (gst_treatment in ('regular', 'composition', 'uin_holders') and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess'])):
if is_reverse_charge_tax(line):
return 'purchase_b2b_rcm'
return 'purchase_b2b_regular'
if not is_reverse_charge_tax(line) and (
gst_treatment == 'deemed_export' and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess'])
or gst_treatment == 'special_economic_zone' and tags_have_categ(line_tags, ['igst', 'cess'])
):
return 'purchase_b2b_regular'
# B2C Unregistered or Consumer sales with gst tags
if gst_treatment in ('unregistered', 'consumer') and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess']) and is_reverse_charge_tax(line):
return 'purchase_b2c_rcm'
# export service type products purchases
if gst_treatment == 'overseas' and any(tax.tax_scope == 'service' for tax in line.tax_ids | line.tax_line_id) and tags_have_categ(line_tags, ['igst', 'cess']):
return 'purchase_imp_services'
# export goods type products purchases
if gst_treatment == 'overseas' and tags_have_categ(line_tags, ['igst', 'cess']) and not is_reverse_charge_tax(line):
return 'purchase_imp_goods'
if not is_bill:
# credit notes for b2b purchases
if gst_treatment in ('regular', 'composition', 'uin_holders') and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess']):
if is_reverse_charge_tax(line):
return 'purchase_cdnr_rcm'
return 'purchase_cdnr_regular'
# credit notes for b2c purchases
if gst_treatment in ('unregistered', 'consumer') and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess']) and is_reverse_charge_tax(line):
return 'purchase_cdnur_rcm'
if not is_reverse_charge_tax(line):
if gst_treatment == 'deemed_export' and tags_have_categ(line_tags, ['sgst', 'cgst', 'igst', 'cess'])\
or gst_treatment == 'special_economic_zone' and tags_have_categ(line_tags, ['igst', 'cess']):
return 'purchase_cdnr_regular'
if gst_treatment == 'overseas' and tags_have_categ(line_tags, ['igst', 'cess']):
return 'purchase_cdnur_regular'
# If none of the above match, default to out of scope
return 'purchase_out_of_scope'
indian_sale_moves_lines = self.filtered(
lambda l: l.move_id.country_code == 'IN'
and l.move_id.is_sale_document(include_receipts=True)
and l.display_type in ('product', 'tax')
)
indian_moves_purchase_lines = self.filtered(
lambda l: l.move_id.country_code == 'IN'
and l.move_id.is_purchase_document(include_receipts=True)
and l.display_type in ('product', 'tax')
)
# No Indian sale or purchase lines to process
if not indian_sale_moves_lines and not indian_moves_purchase_lines:
return {}
move_lines_by_gstr_section = {
**indian_sale_moves_lines.grouped(get_sales_section),
**indian_moves_purchase_lines.grouped(get_purchase_section),
}
return move_lines_by_gstr_section
def _set_l10n_in_gstr_section(self, tax_tags_dict):
move_lines_by_gstr_section = self._get_l10n_in_gstr_section(tax_tags_dict)
if move_lines_by_gstr_section:
for gstr_section, move_lines in move_lines_by_gstr_section.items():
move_lines.l10n_in_gstr_section = gstr_section

View file

@ -0,0 +1,31 @@
from odoo import fields, models
class AccountPayment(models.Model):
_inherit = "account.payment"
# withholding related fields
l10n_in_withhold_move_ids = fields.One2many(
'account.move', 'l10n_in_withholding_ref_payment_id',
string="Indian Payment TDS Entries",
)
l10n_in_total_withholding_amount = fields.Monetary(compute='_compute_l10n_in_total_withholding_amount')
l10n_in_tds_feature_enabled = fields.Boolean(related='company_id.l10n_in_tds_feature')
def _compute_l10n_in_total_withholding_amount(self):
for payment in self:
if payment.company_id.l10n_in_tds_feature:
payment.l10n_in_total_withholding_amount = sum(payment.l10n_in_withhold_move_ids.filtered(
lambda m: m.state == 'posted').l10n_in_withholding_line_ids.mapped('l10n_in_withhold_tax_amount'))
else:
payment.l10n_in_total_withholding_amount = 0.0
def action_l10n_in_withholding_entries(self):
self.ensure_one()
return {
'name': "TDS Entries",
'type': 'ir.actions.act_window',
'res_model': 'account.move',
'view_mode': 'list,form',
'domain': [('id', 'in', self.l10n_in_withhold_move_ids.ids)],
}

View file

@ -0,0 +1,143 @@
from collections import defaultdict
from odoo import api, fields, models
from odoo.tools import frozendict
class AccountTax(models.Model):
_inherit = 'account.tax'
l10n_in_reverse_charge = fields.Boolean("Reverse charge", help="Tick this if this tax is reverse charge. Only for Indian accounting")
l10n_in_gst_tax_type = fields.Selection(
selection=[('igst', 'igst'), ('cgst', 'cgst'), ('sgst', 'sgst'), ('cess', 'cess')],
compute='_compute_l10n_in_gst_tax_type',
)
l10n_in_is_lut = fields.Boolean(
string="LUT",
help="Tick this if this tax is used in LUT (Letter of Undertaking) transactions. Only for Indian accounting.",
)
l10n_in_tax_type = fields.Selection(
selection=[
('gst', 'GST'),
('tcs', 'TCS'),
('tds_sale', 'TDS Sale'),
('tds_purchase', 'TDS Purchase'),
('nil_rated', 'Nil Rated'),
('exempt', 'Exempt'),
('non_gst', 'Non-GST'),
],
string="Indian Tax Type",
)
# withholding related fields
l10n_in_section_id = fields.Many2one('l10n_in.section.alert', string="Section")
l10n_in_tds_feature_enabled = fields.Boolean(related='company_id.l10n_in_tds_feature')
l10n_in_tcs_feature_enabled = fields.Boolean(related='company_id.l10n_in_tcs_feature')
@api.depends('country_code', 'invoice_repartition_line_ids.tag_ids')
def _compute_l10n_in_gst_tax_type(self):
self.l10n_in_gst_tax_type = False
in_taxes = self.filtered(lambda tax: tax.country_code == 'IN')
if in_taxes:
tags_mapping = {
'igst': self.env.ref('l10n_in.tax_tag_igst'),
'cgst': self.env.ref('l10n_in.tax_tag_cgst'),
'sgst': self.env.ref('l10n_in.tax_tag_sgst'),
'cess': self.env.ref('l10n_in.tax_tag_cess'),
}
for tax in in_taxes:
tags = tax.invoice_repartition_line_ids.tag_ids
for tag_code, tag in tags_mapping.items():
if tag in tags:
tax.l10n_in_gst_tax_type = tag_code
break
# -------------------------------------------------------------------------
# HELPERS IN BOTH PYTHON/JAVASCRIPT (hsn_summary.js / account_tax.py)
# HSN SUMMARY
# -------------------------------------------------------------------------
def _prepare_base_line_for_taxes_computation(self, record, **kwargs):
# EXTENDS 'account'
results = super()._prepare_base_line_for_taxes_computation(record, **kwargs)
results['l10n_in_hsn_code'] = self._get_base_line_field_value_from_record(record, 'l10n_in_hsn_code', kwargs, False)
return results
@api.model
def _l10n_in_get_hsn_summary_table(self, base_lines, display_uom):
l10n_in_gst_tax_types = set()
items_map = defaultdict(lambda: {
'quantity': 0.0,
'amount_untaxed': 0.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
})
def get_base_line_grouping_key(base_line):
unique_taxes_data = set(
tax_data['tax']
for tax_data in base_line['tax_details']['taxes_data']
if tax_data['tax']['l10n_in_gst_tax_type'] in ('igst', 'cgst', 'sgst')
)
rate = sum(tax.amount for tax in unique_taxes_data)
return {
'l10n_in_hsn_code': base_line['l10n_in_hsn_code'],
'uom_name': base_line['product_uom_id'].name,
'rate': rate,
}
# quantity / amount_untaxed.
for base_line in base_lines:
key = frozendict(get_base_line_grouping_key(base_line))
if not key['l10n_in_hsn_code']:
continue
item = items_map[key]
item['quantity'] += base_line['quantity']
item['amount_untaxed'] += (
base_line['tax_details']['total_excluded_currency']
+ base_line['tax_details']['delta_total_excluded_currency']
)
# Tax amounts.
def grouping_function(base_line, tax_data):
return {
**get_base_line_grouping_key(base_line),
'l10n_in_gst_tax_type': tax_data['tax'].l10n_in_gst_tax_type,
} if tax_data else None
base_lines_aggregated_values = self._aggregate_base_lines_tax_details(base_lines, grouping_function)
values_per_grouping_key = self._aggregate_base_lines_aggregated_values(base_lines_aggregated_values)
for grouping_key, values in values_per_grouping_key.items():
if (
not grouping_key
or not grouping_key['l10n_in_hsn_code']
or not grouping_key['l10n_in_gst_tax_type']
):
continue
key = frozendict({
'l10n_in_hsn_code': grouping_key['l10n_in_hsn_code'],
'rate': grouping_key['rate'],
'uom_name': grouping_key['uom_name'],
})
item = items_map[key]
l10n_in_gst_tax_type = grouping_key['l10n_in_gst_tax_type']
item[f'tax_amount_{l10n_in_gst_tax_type}'] += values['tax_amount_currency']
l10n_in_gst_tax_types.add(l10n_in_gst_tax_type)
return {
'has_igst': 'igst' in l10n_in_gst_tax_types,
'has_gst': bool({'cgst', 'sgst'} & l10n_in_gst_tax_types),
'has_cess': 'cess' in l10n_in_gst_tax_types,
'nb_columns': 5 + len(l10n_in_gst_tax_types),
'display_uom': display_uom,
'items': [
key | values
for key, values in items_map.items()
],
}

View file

@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
class AccountChartTemplate(models.Model):
_inherit = 'account.chart.template'
def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
res = super(AccountChartTemplate, self)._prepare_all_journals(acc_template_ref, company, journals_dict=journals_dict)
if self == self.env.ref('l10n_in.indian_chart_template_standard'):
for journal in res:
if journal.get('type') in ('sale','purchase'):
journal['l10n_in_gstin_partner_id'] = company.partner_id.id
return res
def _load(self, company):
res = super(AccountChartTemplate, self)._load(company)
if self == self.env.ref("l10n_in.indian_chart_template_standard"):
company.write({
'account_opening_date': fields.Date.context_today(self).replace(month=4, day=1),
'fiscalyear_last_month': '3',
})
return res
class AccountTaxTemplate(models.Model):
_inherit = 'account.tax.template'
l10n_in_reverse_charge = fields.Boolean("Reverse charge", help="Tick this if this tax is reverse charge. Only for Indian accounting")
def _get_tax_vals(self, company, tax_template_to_tax):
val = super(AccountTaxTemplate, self)._get_tax_vals(company, tax_template_to_tax)
if self.tax_group_id:
val['l10n_in_reverse_charge'] = self.l10n_in_reverse_charge
return val

View file

@ -0,0 +1,195 @@
import pytz
from stdnum.in_ import pan, gstin
from odoo import Command, _, api, fields, models
from odoo.exceptions import RedirectWarning
class ResCompany(models.Model):
_inherit = 'res.company'
l10n_in_upi_id = fields.Char(string="UPI Id")
l10n_in_hsn_code_digit = fields.Selection(
selection=[
("4", "4 Digits (turnover < 5 CR.)"),
("6", "6 Digits (turnover > 5 CR.)"),
("8", "8 Digits"),
],
string="HSN Code Digit",
compute="_compute_l10n_in_hsn_code_digit",
store=True,
readonly=False,
)
l10n_in_edi_production_env = fields.Boolean(
string="Indian Production Environment",
help="Enable the use of production credentials",
groups="base.group_system",
default=True,
)
l10n_in_pan_entity_id = fields.Many2one(
related="partner_id.l10n_in_pan_entity_id",
string="PAN",
store=True,
readonly=False,
help="PAN enables the department to link all transactions of the person with the department.\n"
"These transactions include taxpayments, TDS/TCS credits, returns of income/wealth/gift/FBT,"
"specified transactions, correspondence, and so on.\n"
"Thus, PAN acts as an identifier for the person with the tax department.",
)
l10n_in_pan_type = fields.Selection(related="l10n_in_pan_entity_id.type", string="PAN Type")
l10n_in_tan = fields.Char(related="partner_id.l10n_in_tan", string="TAN", readonly=False)
l10n_in_gst_state_warning = fields.Char(related="partner_id.l10n_in_gst_state_warning")
# TDS/TCS settings
l10n_in_tds_feature = fields.Boolean(
string="TDS",
compute="_compute_l10n_in_parent_based_features",
inverse="_inverse_l10n_in_tds_feature",
recursive=True,
store=True,
)
l10n_in_tcs_feature = fields.Boolean(
string="TCS",
compute="_compute_l10n_in_parent_based_features",
inverse="_inverse_l10n_in_tcs_feature",
recursive=True,
store=True,
)
l10n_in_withholding_account_id = fields.Many2one(
comodel_name='account.account',
string="TDS Account",
check_company=True,
)
l10n_in_withholding_journal_id = fields.Many2one(
comodel_name='account.journal',
string="TDS Journal",
check_company=True,
)
# GST settings
l10n_in_is_gst_registered = fields.Boolean(
string="Registered Under GST",
compute="_compute_l10n_in_parent_based_features",
inverse="_inverse_l10n_in_is_gst_registered",
recursive=True,
store=True,
)
l10n_in_gstin_status_feature = fields.Boolean(string="Check GST Number Status")
def _inverse_l10n_in_tds_feature(self):
for company in self:
self._activate_l10n_in_taxes(['tds_group'], company, company.l10n_in_tds_feature)
def _inverse_l10n_in_tcs_feature(self):
for company in self:
self._activate_l10n_in_taxes(['tcs_group'], company, company.l10n_in_tcs_feature)
def _inverse_l10n_in_is_gst_registered(self):
for company in self:
gst_group_refs = [
'sgst_group',
'cgst_group',
'igst_group',
'cess_group',
'gst_group',
'exempt_group',
'nil_rated_group',
'non_gst_supplies_group',
]
if company.l10n_in_is_gst_registered:
self._activate_l10n_in_taxes(gst_group_refs, company, True)
# Set sale and purchase tax accounts when user registered under GST.
company.account_sale_tax_id = self.env['account.chart.template'].with_company(company).ref('sgst_sale_5', raise_if_not_found=False)
company.account_purchase_tax_id = self.env['account.chart.template'].with_company(company).ref('sgst_purchase_5', raise_if_not_found=False)
else:
self._activate_l10n_in_taxes(gst_group_refs, company, False)
company.account_sale_tax_id = False
company.account_purchase_tax_id = False
@api.depends('parent_id.l10n_in_tds_feature', 'parent_id.l10n_in_tcs_feature', 'parent_id.l10n_in_is_gst_registered')
def _compute_l10n_in_parent_based_features(self):
for company in self:
if company.parent_id:
company.l10n_in_tds_feature = company.parent_id.l10n_in_tds_feature
company.l10n_in_tcs_feature = company.parent_id.l10n_in_tcs_feature
company.l10n_in_is_gst_registered = company.parent_id.l10n_in_is_gst_registered
def _activate_l10n_in_taxes(self, group_refs, company, active=True):
tax_group_ids = [
tax_group.id
for group_ref in group_refs
if (tax_group := self.env['account.chart.template'].with_company(company).ref(group_ref, raise_if_not_found=False))
]
if tax_group_ids:
taxes = self.env['account.tax'].with_company(company).with_context(active_test=False).search([
('tax_group_id', 'in', tax_group_ids),
('active', '!=', active)
])
taxes.write({'active': active})
@api.depends('vat')
def _compute_l10n_in_hsn_code_digit(self):
for record in self:
if record.country_code == "IN" and record.vat:
record.l10n_in_hsn_code_digit = "4"
else:
record.l10n_in_hsn_code_digit = False
@api.onchange('vat')
def onchange_vat(self):
self.partner_id.onchange_vat()
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
# Update Fiscal Positions for new branch
res._update_l10n_in_fiscal_position()
return res
def write(self, vals):
res = super().write(vals)
if vals.get('vat'):
# Enable GST(l10n_in_is_gst_registered) when a valid GSTIN(vat) is applied.
self._update_l10n_in_is_gst_registered()
if (vals.get('state_id') or vals.get('country_id')) and not self.env.context.get('delay_account_group_sync'):
# Update Fiscal Positions for companies setting up state for the first time
self._update_l10n_in_fiscal_position()
return res
def _update_l10n_in_fiscal_position(self):
companies_need_update_fp = self.filtered(lambda c: c.parent_ids[0].chart_template == 'in')
for company in companies_need_update_fp:
ChartTemplate = self.env['account.chart.template'].with_company(company)
fiscal_position_data = ChartTemplate._get_in_account_fiscal_position()
for values in fiscal_position_data.values():
values['tax_ids'] = [Command.set([
xml_id
for xml_id in values['tax_ids'][0][2]
if ChartTemplate.ref(xml_id, raise_if_not_found=False)
])]
ChartTemplate._load_data({'account.fiscal.position': fiscal_position_data})
def _update_l10n_in_is_gst_registered(self):
for company in self:
if company.country_code == "IN" and company.vat:
company.l10n_in_is_gst_registered = company.partner_id.check_vat_in(company.vat)
def action_update_state_as_per_gstin(self):
self.ensure_one()
self.partner_id.action_update_state_as_per_gstin()
def _check_tax_return_configuration(self):
"""
Check if the company is properly configured for tax returns.
:raises RedirectWarning: if something is wrong configured.
"""
if self.country_code != 'IN':
return super()._check_tax_return_configuration()
is_l10n_in_reports_installed = 'l10n_in_reports' in self.env['ir.module.module']._installed()
if not is_l10n_in_reports_installed:
msg = _("First enable GST e-Filing feature from configuration for company %s.", (self.name))
action = self.env.ref("account.action_account_config")
raise RedirectWarning(msg, action.id, _('Go to configuration'))

View file

@ -0,0 +1,30 @@
from odoo import api, models
from odoo.addons.iap import jsonrpc
DEFAULT_IAP_ENDPOINT = "https://l10n-in-edi.api.odoo.com"
DEFAULT_IAP_TEST_ENDPOINT = "https://l10n-in-edi-demo.api.odoo.com"
IAP_SERVICE_NAME = 'l10n_in_edi'
TEST_GST_NUMBER = '24FANCY1234AAZA'
class IapAccount(models.Model):
_inherit = 'iap.account'
@api.model
def _l10n_in_connect_to_server(self, is_production, params, url_path, config_parameter, timeout=25):
IrConfigParam = self.env['ir.config_parameter'].sudo()
user_token = self.get(IAP_SERVICE_NAME)
params.update({
"dbuuid": IrConfigParam.get_param("database.uuid"),
"account_token": user_token.sudo().account_token,
})
gsp_provider = IrConfigParam.get_param("l10n_in.gsp_provider")
if gsp_provider:
params.update({"gsp_provider": gsp_provider})
if params.get('gstin') == TEST_GST_NUMBER:
default_endpoint = DEFAULT_IAP_TEST_ENDPOINT
else:
default_endpoint = DEFAULT_IAP_ENDPOINT if is_production else DEFAULT_IAP_TEST_ENDPOINT
endpoint = IrConfigParam.get_param(config_parameter, default_endpoint)
url = "%s%s" % (endpoint, url_path)
return jsonrpc(url, params=params, timeout=timeout)

View file

@ -0,0 +1,92 @@
import base64
from stdnum.in_ import pan
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class L10nInPanEntity(models.Model):
_name = 'l10n_in.pan.entity'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Indian PAN Entity'
name = fields.Char(string="PAN", tracking=1, required=True)
type = fields.Selection([
('a', 'Association of Persons'),
('b', 'Body of Individuals'),
('c', 'Company'),
('f', 'Firms'),
('g', 'Government'),
('h', 'Hindu Undivided Family'),
('j', 'Artificial Judicial Person'),
('l', 'Local Authority'),
('p', 'Individual'),
('t', 'Association of Persons for a Trust'),
('k', 'Krish (Trust Krish)'),
], compute='_compute_type', readonly=True, store=True)
partner_ids = fields.One2many(
comodel_name='res.partner',
inverse_name='l10n_in_pan_entity_id',
string="Partners",
domain="[('l10n_in_pan_entity_id', '=', False), '|', ('vat', '=', False), ('vat', 'like', name)]"
)
tds_deduction = fields.Selection([
('normal', 'Normal'),
('lower', 'Lower'),
('higher', 'Higher'),
('no', 'No'),
], string="TDS Deduction", default='normal', tracking=2)
tds_certificate = fields.Binary(string="TDS Certificate", copy=False)
tds_certificate_filename = fields.Char(string="TDS Certificate Filename", copy=False)
# MSME/Udyam Registration details
msme_type = fields.Selection([
("micro", "Micro"),
("small", "Small"),
("medium", "Medium")
], string="MSME/Udyam Registration Type", copy=False)
msme_number = fields.Char(string="MSME/Udyam Registration Number", copy=False)
_name_uniq = models.Constraint(
'unique (name)',
'A PAN Entity with same PAN Number already exists.',
)
@api.constrains('name')
def _check_pan_name(self):
if 'import_file' in self.env.context:
return
for record in self:
if record.name and not pan.is_valid(record.name):
raise ValidationError(_("The entered PAN %s seems invalid. Please enter a valid PAN.", record.name))
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
for record in records:
record.name = record.name.upper()
return records
def write(self, vals):
if vals.get('name'):
vals['name'] = vals['name'].upper()
res = super().write(vals)
if vals.get('tds_certificate'):
for rec in self:
rec.message_post(
body=_("TDS Certificate Added"),
message_type='notification',
subtype_xmlid='mail.mt_note',
attachments=[(rec.tds_certificate_filename, base64.b64decode(vals['tds_certificate']))]
)
return res
@api.depends('name')
def _compute_type(self):
for record in self:
if record.name:
if pan.is_valid(record.name):
record.type = record.name[3].lower()
else:
record.type = False

View file

@ -0,0 +1,17 @@
from odoo import models
class AccountReport(models.Model):
_inherit = 'account.report'
def _init_options_buttons(self, options, previous_options):
super()._init_options_buttons(options, previous_options)
company = self.env.company
generic_report_id = self.env.ref('account.generic_tax_report').id
# Remove 'Returns' button from generic report for indian company
if company.country_id.code == 'IN' and self.id == generic_report_id and not self.root_report_id:
options['buttons'] = [
button for button in options['buttons']
if button.get('action') != 'action_open_returns'
]

View file

@ -0,0 +1,50 @@
from odoo import _, api, fields, models
class L10n_InSectionAlert(models.Model):
_name = 'l10n_in.section.alert'
_description = "indian section alert"
name = fields.Char("Section Name")
tax_source_type = fields.Selection([
('tds', 'TDS'),
('tcs', 'TCS'),
], string="Tax Source Type")
consider_amount = fields.Selection([
('untaxed_amount', 'Untaxed Amount'),
('total_amount', 'Total Amount'),
], string="Consider", default='untaxed_amount', required=True)
is_per_transaction_limit = fields.Boolean("Per Transaction")
per_transaction_limit = fields.Float("Per Transaction limit")
is_aggregate_limit = fields.Boolean("Aggregate")
aggregate_limit = fields.Float("Aggregate limit")
aggregate_period = fields.Selection([
('monthly', 'Monthly'),
('fiscal_yearly', 'Financial Yearly'),
], string="Aggregate Period", default='fiscal_yearly')
l10n_in_section_tax_ids = fields.One2many("account.tax", "l10n_in_section_id", string="Taxes")
tax_report_line_id = fields.Many2one(string="Tax Report Line", comodel_name='account.report.line')
_per_transaction_limit = models.Constraint(
'CHECK(per_transaction_limit >= 0)',
'Per transaction limit must be positive',
)
_aggregate_limit = models.Constraint(
'CHECK(aggregate_limit >= 0)',
'Aggregate limit must be positive',
)
@api.depends('tax_source_type')
def _compute_display_name(self):
for record in self:
record.display_name = f"{record.tax_source_type.upper()} {record.name or ''}" if record.tax_source_type else f"{record.name or ''}"
def _get_warning_message(self):
warning = ", ".join(self.mapped('name'))
section_type = next(iter(set(self.mapped('tax_source_type')))).upper()
action = _('collect') if section_type == 'TCS' else _('deduct')
return _("It's advisable to %(action)s %(section_type)s u/s %(warning)s on this transaction.",
action=action,
section_type=section_type,
warning=warning
)

View file

@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from markupsafe import Markup
from odoo import fields, api, models, _
from odoo.exceptions import UserError
class Message(models.Model):
_inherit = 'mail.message'
l10n_in_audit_log_preview = fields.Html(string="Description", compute="_compute_l10n_in_audit_log_preview")
l10n_in_audit_log_account_move_id = fields.Many2one('account.move', string="Journal Entry", compute="_compute_l10n_in_audit_log_document_name", search="_search_l10n_in_audit_log_document_name")
@api.depends('body', 'subject', 'tracking_value_ids', 'subtype_id')
def _compute_l10n_in_audit_log_preview(self):
for message in self:
title = message.subject or message.preview
tracking_value_ids = message.sudo().tracking_value_ids
if not title and tracking_value_ids:
title = _("Updated")
elif not title and message.subtype_id and not message.subtype_id.internal:
title = message.subtype_id.display_name
audit_log_preview = Markup("<div>%s</div>") % (title)
for value in tracking_value_ids:
audit_log_preview += Markup(
"<li>%(old_value)s <i class='o_TrackingValue_separator fa fa-long-arrow-right mx-1 text-600' title='%(title)s' role='img' aria-label='%(title)s'></i>%(new_value)s (%(field)s)</li>"
) % {
'old_value': value._get_old_display_value()[0] or _("None"),
'new_value': value._get_new_display_value()[0] or _("None"),
'title': _("Changed"),
'field': value.field.field_description,
}
message.l10n_in_audit_log_preview = audit_log_preview
@api.depends('model', 'res_id')
def _compute_l10n_in_audit_log_document_name(self):
messages_of_account_move = self.filtered(lambda m: m.model == 'account.move' and m.res_id)
(self - messages_of_account_move).l10n_in_audit_log_account_move_id = False
moves = self.env['account.move'].search([('id', 'in', messages_of_account_move.mapped('res_id'))])
moves_by_id = {m.id: m for m in moves}
for message in messages_of_account_move:
message.l10n_in_audit_log_account_move_id = moves_by_id.get(message.res_id, False)
def _search_l10n_in_audit_log_document_name(self, operator, value):
is_set = False
if operator == '!=' and isinstance(value, bool):
is_set = True
elif operator not in ['=', 'ilike'] or not isinstance(value, str):
raise UserError(_('Operation not supported'))
move_domain = [('company_id.account_fiscal_country_id.code', '=', 'IN')]
if not is_set:
move_domain += [('name', operator, value)]
move_query = self.env['account.move']._search(move_domain)
return [('model', '=', 'account.move'), ('res_id', 'in', move_query)]

View file

@ -1,10 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class L10nInPortCode(models.Model):
class L10n_InPortCode(models.Model):
"""Port code must be mentioned in export and import of goods under GST."""
_name = 'l10n_in.port.code'
_description = "Indian port code"
@ -14,6 +11,7 @@ class L10nInPortCode(models.Model):
name = fields.Char(string="Port", required=True)
state_id = fields.Many2one('res.country.state', string="State")
_sql_constraints = [
('code_uniq', 'unique (code)', 'The Port Code must be unique!')
]
_code_uniq = models.Constraint(
'unique (code)',
'The Port Code must be unique!',
)

View file

@ -1,11 +1,41 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields
import re
from odoo import _, api, models, fields
class ProductTemplate(models.Model):
_inherit = 'product.template'
l10n_in_hsn_code = fields.Char(string="HSN/SAC Code", help="Harmonized System Nomenclature/Services Accounting Code")
l10n_in_hsn_description = fields.Char(string="HSN/SAC Description", help="HSN/SAC description is required if HSN/SAC code is not provided.")
l10n_in_hsn_warning = fields.Text(string="HSC/SAC warning", compute="_compute_l10n_in_hsn_warning")
l10n_in_is_gst_registered_enabled = fields.Boolean(compute="_compute_l10n_in_is_gst_registered_enabled")
@api.depends('company_id.l10n_in_is_gst_registered')
@api.depends_context('allowed_company_ids')
def _compute_l10n_in_is_gst_registered_enabled(self):
for record in self:
allowed_companies = record.company_id or self.env.companies
record.l10n_in_is_gst_registered_enabled = any(
company.l10n_in_is_gst_registered
for company in allowed_companies
)
@api.depends('sale_ok', 'l10n_in_hsn_code')
def _compute_l10n_in_hsn_warning(self):
digit_suffixes = {
'4': _("either 4, 6 or 8"),
'6': _("either 6 or 8"),
'8': _("8")
}
active_hsn_code_digit_len = max(
int(company.l10n_in_hsn_code_digit)
for company in self.env.companies
)
for record in self:
check_hsn = record.sale_ok and record.l10n_in_hsn_code and active_hsn_code_digit_len
if check_hsn and (not re.match(r'^\d{4}$|^\d{6}$|^\d{8}$', record.l10n_in_hsn_code) or len(record.l10n_in_hsn_code) < active_hsn_code_digit_len):
record.l10n_in_hsn_warning = _(
"HSN code field must consist solely of digits and be %s in length.",
digit_suffixes.get(str(active_hsn_code_digit_len))
)
continue
record.l10n_in_hsn_warning = False

View file

@ -1,10 +1,190 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, fields, models
from odoo.exceptions import UserError, ValidationError, RedirectWarning
from odoo.tools.sql import column_exists, create_column
from odoo.tools import SQL
from odoo import api, fields, models
from odoo.addons.l10n_in.models.iap_account import IAP_SERVICE_NAME
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
group_l10n_in_reseller = fields.Boolean(implied_group='l10n_in.group_l10n_in_reseller', string="Manage Reseller(E-Commerce)")
group_l10n_in_reseller = fields.Boolean(
implied_group='l10n_in.group_l10n_in_reseller',
string="Manage Reseller(E-Commerce)"
)
l10n_in_edi_production_env = fields.Boolean(
string="Indian Production Environment",
related="company_id.l10n_in_edi_production_env",
readonly=False
)
l10n_in_gsp = fields.Selection(selection=[
('bvm', 'BVM IT Consulting'),
('tera', 'Tera Software (Deprecated)'),
], string="GSP",
inverse="_set_l10n_in_gsp", # use an inverse method to invalidate existing tokens if the GSP is changed
store=False,
help="Select the GST Suvidha Provider (GSP) you want to use for GST services.",
)
l10n_in_hsn_code_digit = fields.Selection(
related='company_id.l10n_in_hsn_code_digit',
readonly=False
)
# TDS/TCS settings
l10n_in_tds_feature = fields.Boolean(
related='company_id.l10n_in_tds_feature',
readonly=False
)
l10n_in_tcs_feature = fields.Boolean(
related='company_id.l10n_in_tcs_feature',
readonly=False
)
l10n_in_withholding_account_id = fields.Many2one(
related='company_id.l10n_in_withholding_account_id',
readonly=False
)
l10n_in_withholding_journal_id = fields.Many2one(
related='company_id.l10n_in_withholding_journal_id',
readonly=False
)
l10n_in_tan = fields.Char(
related='company_id.l10n_in_tan',
readonly=False
)
# GST settings
l10n_in_is_gst_registered = fields.Boolean(
related='company_id.l10n_in_is_gst_registered',
readonly=False
)
l10n_in_gstin = fields.Char(
string="GST Number",
related='company_id.vat',
readonly=False
)
l10n_in_gstin_status_feature = fields.Boolean(
related='company_id.l10n_in_gstin_status_feature',
readonly=False
)
l10n_in_gst_efiling_feature = fields.Boolean(string="GST E-Filing & Matching Feature")
l10n_in_fetch_vendor_edi_feature = fields.Boolean(string="Fetch Vendor E-Invoiced Document")
l10n_in_enet_vendor_batch_payment_feature = fields.Boolean(string="ENet Vendor Batch Payment")
module_l10n_in_reports = fields.Boolean("GST E-Filing & Matching")
module_l10n_in_edi = fields.Boolean("Indian Electronic Invoicing")
module_l10n_in_ewaybill = fields.Boolean("Indian Electronic Waybill")
def set_values(self):
super().set_values()
if self.country_code == 'IN':
if (
not self.module_l10n_in_reports
and (
self.l10n_in_fetch_vendor_edi_feature
or self.l10n_in_gst_efiling_feature
or self.l10n_in_enet_vendor_batch_payment_feature
)
):
self.module_l10n_in_reports = True
for l10n_in_feature in (
"l10n_in_fetch_vendor_edi_feature",
"l10n_in_gst_efiling_feature",
"l10n_in_enet_vendor_batch_payment_feature",
):
if self[l10n_in_feature]:
self._update_l10n_in_feature(l10n_in_feature)
if self.module_l10n_in_edi:
self._update_l10n_in_feature("l10n_in_edi_feature")
if self.module_l10n_in_ewaybill:
self._update_l10n_in_feature("l10n_in_ewaybill_feature")
def _update_l10n_in_feature(self, column):
""" This way, after installing the module, the field will already be set for the active company. """
if not column_exists(self.env.cr, "res_company", column):
create_column(self.env.cr, "res_company", column, "boolean")
self.env.cr.execute(SQL(
f"""
UPDATE res_company
SET {column} = true
WHERE id = {self.env.company.id}
"""
))
def l10n_in_edi_buy_iap(self):
if (
not self.l10n_in_edi_production_env
or not (
self.module_l10n_in_edi
or self.module_l10n_in_ewaybill
or self.l10n_in_gstin_status_feature
or self.l10n_in_gst_efiling_feature
)
):
raise ValidationError(_(
"Please ensure that at least one Indian service and production environment is enabled,"
" and save the configuration to proceed with purchasing credits."
))
return {
'type': 'ir.actions.act_url',
'url': self.env["iap.account"].get_credits_url(service_name=IAP_SERVICE_NAME),
'target': '_new'
}
def _l10n_in_check_gst_number(self):
company = self.company_id
if not company.partner_id.check_vat_in(company.vat):
action = {
'view_mode': 'form',
'res_model': 'res.company',
'type': 'ir.actions.act_window',
'res_id': company.id,
'views': [[self.env.ref('base.view_company_form').id, 'form']],
}
raise RedirectWarning(_("Please set a valid GST number on company."), action, _("Go to Company"))
def reload_template(self):
super().reload_template()
if self.country_code == 'IN':
branch_companies = self.company_id.child_ids
if branch_companies:
branch_companies._update_l10n_in_fiscal_position()
def _l10n_in_is_first_time_setup(self):
"""
Check if at least one company for India has been configured with the localization settings.
If not, it means it's the first time setup.
"""
all_validity_fields = ['l10n_in_gstr_gst_token_validity', 'l10n_in_edi_token_validity', 'l10n_in_ewaybill_auth_validity']
validity_fields = (field_name for field_name in self.company_id._fields if field_name in all_validity_fields)
if validity_fields:
validity_fields_domain = fields.Domain.OR([[(field_name, '!=', False)] for field_name in validity_fields])
configured_company_count = self.env['res.company'].sudo().search_count([
('account_fiscal_country_id.code', '=', 'IN'),
*validity_fields_domain
])
return not configured_company_count
return True
def get_values(self):
res = super().get_values()
res['l10n_in_gsp'] = self.env['ir.config_parameter'].sudo().get_param('l10n_in.gsp_provider')
if not res['l10n_in_gsp']:
if self._l10n_in_is_first_time_setup():
# Default to BVM for new databases setting up India localization for the first time
res['l10n_in_gsp'] = 'bvm'
else:
res['l10n_in_gsp'] = 'tera'
return res
def _l10n_in_gsp_provider_changed(self):
""" Hook to be overridden in other modules to handle GSP provider change. """
self.ensure_one()
self.env['ir.config_parameter'].sudo().set_param('l10n_in.gsp_provider', self.l10n_in_gsp)
def _set_l10n_in_gsp(self):
gsp_before = self.env['ir.config_parameter'].sudo().get_param('l10n_in.gsp_provider')
for config in self:
if gsp_before != config.l10n_in_gsp:
config._l10n_in_gsp_provider_changed()
return

View file

@ -1,10 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class CountryState(models.Model):
class ResCountryState(models.Model):
_inherit = 'res.country.state'
l10n_in_tin = fields.Char('TIN Number', size=2, help="TIN number-first two digits")
l10n_in_tin = fields.Char("TIN Number", size=2, help="TIN number-first two digits")

View file

@ -1,9 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import re
from stdnum.in_ import pan
from odoo import api, fields, models, _
from odoo.exceptions import UserError, AccessError, ValidationError
from odoo.addons.l10n_in.models.iap_account import IAP_SERVICE_NAME
from odoo.tools.misc import clean_context
_logger = logging.getLogger(__name__)
TEST_GST_NUMBER = "36AABCT1332L011"
TEST_GST_NUMBER_BVM = "29AAGCB1286Q000"
class ResPartner(models.Model):
_inherit = 'res.partner'
@ -19,33 +27,206 @@ class ResPartner(models.Model):
('uin_holders', 'UIN Holders'),
], string="GST Treatment")
@api.onchange('company_type')
def onchange_company_type(self):
res = super().onchange_company_type()
if self.country_id and self.country_id.code == 'IN':
self.l10n_in_gst_treatment = (self.company_type == 'company') and 'regular' or 'consumer'
l10n_in_pan_entity_id = fields.Many2one(
comodel_name='l10n_in.pan.entity',
string="PAN",
ondelete='restrict',
help="PAN enables the department to link all transactions of the person with the department.\n"
"These transactions include taxpayments, TDS/TCS credits, returns of income/wealth/gift/FBT,"
" specified transactions, correspondence, and so on.\n"
"Thus, PAN acts as an identifier for the person with the tax department."
)
l10n_in_tan = fields.Char("TAN")
display_pan_warning = fields.Boolean(string="Display pan warning", compute="_compute_display_pan_warning")
l10n_in_gst_state_warning = fields.Char(compute="_compute_l10n_in_gst_state_warning")
l10n_in_is_gst_registered_enabled = fields.Boolean(compute="_compute_l10n_in_gst_registered_and_status")
# gstin_status related field
l10n_in_gstin_verified_status = fields.Boolean(string="GST Status", tracking=True)
l10n_in_gstin_verified_date = fields.Date(string="GSTIN Verified Date", tracking=True)
l10n_in_gstin_status_feature_enabled = fields.Boolean(compute="_compute_l10n_in_gst_registered_and_status")
@api.depends('vat', 'state_id', 'country_id', 'fiscal_country_codes')
def _compute_l10n_in_gst_state_warning(self):
for partner in self:
if (
"IN" in partner.fiscal_country_codes
and partner.check_vat_in(partner.vat)
):
if partner.vat[:2] == "99":
partner.l10n_in_gst_state_warning = _(
"As per GSTN the country should be other than India, so it's recommended to"
)
else:
state_id = self.env['res.country.state'].search([('l10n_in_tin', '=', partner.vat[:2])], limit=1)
if state_id and state_id != partner.state_id:
partner.l10n_in_gst_state_warning = _(
"As per GSTN the state should be %s, so it's recommended to", state_id.name
)
else:
partner.l10n_in_gst_state_warning = False
else:
partner.l10n_in_gst_state_warning = False
@api.depends('l10n_in_pan_entity_id')
def _compute_display_pan_warning(self):
for partner in self:
partner.display_pan_warning = partner.vat and partner.l10n_in_pan_entity_id and partner.l10n_in_pan_entity_id.name != partner.vat[2:12]
@api.depends('company_id.l10n_in_is_gst_registered', 'company_id.l10n_in_gstin_status_feature')
def _compute_l10n_in_gst_registered_and_status(self):
for record in self:
company = record.company_id or self.env.company
record.l10n_in_is_gst_registered_enabled = company.l10n_in_is_gst_registered
record.l10n_in_gstin_status_feature_enabled = company.l10n_in_gstin_status_feature
@api.onchange('vat')
def _onchange_l10n_in_gst_status(self):
"""
Reset GST Status Whenever the `vat` of partner changes
"""
for partner in self:
if partner.country_code == 'IN' and (partner.l10n_in_gstin_verified_status or partner.l10n_in_gstin_verified_date):
partner.l10n_in_gstin_verified_status = False
partner.l10n_in_gstin_verified_date = False
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
if 'import_file' in self.env.context:
return res
for partner in res.filtered(lambda p: p.country_code == 'IN' and p.vat and p.check_vat_in(p.vat)):
partner._set_l10n_in_pan_tan_from_vat()
return res
@api.onchange('country_id')
def _onchange_country_id(self):
res = super()._onchange_country_id()
if self.country_id and self.country_id.code != 'IN':
self.l10n_in_gst_treatment = 'overseas'
elif self.country_id and self.country_id.code == 'IN':
self.l10n_in_gst_treatment = (self.company_type == 'company') and 'regular' or 'consumer'
def write(self, vals):
res = super().write(vals)
if 'import_file' in self.env.context:
return res
if vals.get('vat') or vals.get('country_id'):
for partner in self.filtered(lambda p: p.country_code == 'IN' and p.vat and p.check_vat_in(p.vat)):
partner._set_l10n_in_pan_tan_from_vat()
return res
def _set_l10n_in_pan_tan_from_vat(self):
self.ensure_one()
identifier = self.vat[2:12].upper()
if pan.is_valid(identifier):
self.l10n_in_pan_entity_id = self._l10n_in_search_create_pan_entity_from_vat(self.vat).id
elif re.match(r'^[A-Z]{4}[0-9]{5}[A-Z]{1}$', identifier):
self.l10n_in_tan = identifier
def _l10n_in_search_create_pan_entity_from_vat(self, vat):
pan_number = vat[2:12].upper()
pan_entity = self.env['l10n_in.pan.entity'].search([('name', '=', pan_number)], limit=1)
if not pan_entity:
context = clean_context(self.env.context)
pan_entity = self.env['l10n_in.pan.entity'].with_context(context).create({'name': pan_number})
return pan_entity
def action_l10n_in_verify_gstin_status(self):
self.ensure_one()
self.check_access('write')
if self.env.company.sudo().account_fiscal_country_id.code != 'IN':
raise UserError(_('You must be logged in an Indian company to use this feature'))
if not self.vat:
raise ValidationError(_("Please enter the GSTIN"))
if not self.env.company.l10n_in_gstin_status_feature:
raise ValidationError(_("This feature is not activated. Go to Settings to activate this feature."))
is_production = self.env.company.sudo().l10n_in_edi_production_env
params = {
"gstin_to_search": self.vat,
"gstin": self.env.company.vat,
}
try:
response = self.env['iap.account']._l10n_in_connect_to_server(
is_production,
params,
'/iap/l10n_in_reports/1/public/search',
"l10n_in.endpoint"
)
except AccessError:
raise UserError(_("Unable to connect with GST network"))
if response.get('error') and any(e.get('code') == 'no-credit' for e in response['error']):
return self.env["bus.bus"]._sendone(self.env.user.partner_id, "iap_notification",
{
"type": "no_credit",
"title": _("Not enough credits to check GSTIN status"),
"get_credits_url": self.env["iap.account"].get_credits_url(service_name=IAP_SERVICE_NAME),
},
)
gst_status = response.get('data', {}).get('sts', "")
if gst_status.casefold() == 'active':
l10n_in_gstin_verified_status = True
elif gst_status:
l10n_in_gstin_verified_status = False
date_from = response.get("data", {}).get("cxdt", '')
if date_from and re.search(r'\d', date_from):
message = _(
"GSTIN %(vat)s is %(status)s and Effective from %(date_from)s.",
vat=self.vat,
status=gst_status,
date_from=date_from,
)
else:
message = _(
"GSTIN %(vat)s is %(status)s, effective date is not available.",
vat=self.vat,
status=gst_status
)
if not is_production:
message += _(" Warning: You are currently in a test environment. The result is a dummy.")
self.message_post(body=message)
else:
_logger.info("GST status check error %s", response)
if response.get('error') and any(e.get('code') == 'SWEB_9035' for e in response['error']):
raise UserError(
_("The provided GSTIN is invalid. Please check the GSTIN and try again.")
)
default_error_message = _(
"Something went wrong while fetching the GST status."
"Please Contact Support if the error persists with"
"Response: %(response)s",
response=response
)
error_messages = [
f"[{error.get('code') or _('Unknown')}] {error.get('message') or default_error_message}"
for error in response.get('error')
]
raise UserError(
error_messages
and '\n'.join(error_messages)
or default_error_message
)
self.write({
"l10n_in_gstin_verified_status": l10n_in_gstin_verified_status,
"l10n_in_gstin_verified_date": fields.Date.today(),
})
return {
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"type": "info",
"message": _("GSTIN Status Updated Successfully"),
"next": {"type": "ir.actions.act_window_close"},
},
}
@api.onchange('vat')
def onchange_vat(self):
if self.vat and self.check_vat_in(self.vat):
self.vat = self.vat.upper()
state_id = self.env['res.country.state'].search([('l10n_in_tin', '=', self.vat[:2])], limit=1)
if state_id:
self.state_id = state_id
pan_entity = self.env['l10n_in.pan.entity'].search([('name', '=', self.vat[2:12])], limit=1)
if pan_entity:
self.l10n_in_pan_entity_id = pan_entity.id
@api.model
def _commercial_fields(self):
res = super()._commercial_fields()
return res + ['l10n_in_gst_treatment']
return super()._commercial_fields() + ['l10n_in_gst_treatment', 'l10n_in_pan_entity_id', 'l10n_in_tan']
def check_vat_in(self, vat):
"""
@ -53,6 +234,28 @@ class ResPartner(models.Model):
but this is not a valid number as per the regular expression
so TEST_GST_NUMBER is considered always valid
"""
if vat == TEST_GST_NUMBER:
if vat in (TEST_GST_NUMBER, TEST_GST_NUMBER_BVM):
return True
return super().check_vat_in(vat)
@api.model
def _l10n_in_get_partner_vals_by_vat(self, vat):
partner_data = self.enrich_by_gst(vat)
for fname in list(partner_data.keys()):
if fname not in self.env['res.partner']._fields:
partner_data.pop(fname, None)
partner_data.update({
'country_id': partner_data.get('country_id', {}).get('id'),
'state_id': partner_data.get('state_id', {}).get('id'),
'company_type': 'company',
'l10n_in_gst_treatment': partner_data.get('l10n_in_gst_treatment', 'regular'),
})
return partner_data
def action_update_state_as_per_gstin(self):
self.ensure_one()
if self.check_vat_in(self.vat):
state_id = self.env['res.country.state'].search([('l10n_in_tin', '=', self.vat[:2])], limit=1)
self.state_id = state_id
if self.ref_company_ids:
self.ref_company_ids._update_l10n_in_fiscal_position()

View file

@ -0,0 +1,143 @@
from odoo import Command, models
from odoo.addons.account.models.chart_template import template
class AccountChartTemplate(models.AbstractModel):
_inherit = 'account.chart.template'
@template('in')
def _get_in_template_data(self):
return {
'property_account_receivable_id': 'p10040',
'property_account_payable_id': 'p11211',
'code_digits': '6',
'display_invoice_amount_total_words': True,
}
@template('in', 'res.company')
def _get_in_res_company(self):
return {
self.env.company.id: {
'account_fiscal_country_id': 'base.in',
'bank_account_code_prefix': '1002',
'cash_account_code_prefix': '1001',
'transfer_account_code_prefix': '1008',
'account_default_pos_receivable_account_id': 'p10041',
'income_currency_exchange_account_id': 'p2013',
'expense_currency_exchange_account_id': 'p2117',
'account_journal_early_pay_discount_loss_account_id': 'p2132',
'account_journal_early_pay_discount_gain_account_id': '2012',
'fiscalyear_last_month': '3',
'account_sale_tax_id': 'sgst_sale_5',
'account_purchase_tax_id': 'sgst_purchase_5',
'deferred_expense_account_id': 'p10084',
'deferred_revenue_account_id': 'p10085',
'expense_account_id': 'p2107',
'income_account_id': 'p20011',
'l10n_in_withholding_account_id': 'p100595',
'tax_calculation_rounding_method': 'round_per_line',
},
}
@template('in', 'account.cash.rounding')
def _get_in_account_cash_rounding(self):
return {
'l10n_in.cash_rounding_in_half_up': {
'profit_account_id': 'p213202',
'loss_account_id': 'p213201',
}
}
@template('in', 'account.fiscal.position')
def _get_in_account_fiscal_position(self):
_ = self.env._
company = self.env.company
state_ids = [Command.set(company.state_id.ids)] if company.state_id else False
intra_state_name = company.state_id and _("Within %s", company.state_id.name) or _("Intra State")
country_in_id = self.env.ref('base.in').id
state_specific = {
'fiscal_position_in_intra_state': {
'name': intra_state_name,
'sequence': 1,
'auto_apply': True,
'state_ids': state_ids,
'tax_ids': self._get_l10n_in_fiscal_tax_vals('fiscal_position_in_intra_state'),
'country_id': country_in_id,
},
'fiscal_position_in_inter_state': {
'name': _("Inter State"),
'sequence': 2,
'auto_apply': True,
'country_group_id': 'l10n_in.inter_state_group',
'tax_ids': self._get_l10n_in_fiscal_tax_vals('fiscal_position_in_inter_state'),
},
}
if company.parent_id:
return {
self.company_xmlid(k): v
for k, v in state_specific.items()
}
return {
**state_specific,
'fiscal_position_in_sez': {
'name': _("Special Economic Zone (SEZ)"),
'sequence': 3,
'auto_apply': True,
'state_ids': [Command.set(self.env.ref('l10n_in.state_in_oc').ids)],
'country_id': country_in_id,
'note': _("SUPPLY MEANT FOR EXPORT/SUPPLY TO SEZ UNIT OR SEZ DEVELOPER FOR AUTHORISED OPERATIONS ON PAYMENT OF INTEGRATED TAX."),
'tax_ids': self._get_l10n_in_fiscal_tax_vals('fiscal_position_in_inter_state'),
},
'fiscal_position_in_export_sez_in': {
'name': _("Export"),
'sequence': 4,
'auto_apply': True,
'note': _("SUPPLY MEANT FOR EXPORT/SUPPLY TO SEZ UNIT OR SEZ DEVELOPER FOR AUTHORISED OPERATIONS ON PAYMENT OF INTEGRATED TAX."),
'tax_ids': self._get_l10n_in_fiscal_tax_vals('fiscal_position_in_export_sez_in'),
},
'fiscal_position_in_lut_sez_1': {
'name': _("SEZ - LUT (WOP)"),
'sequence': 5,
'state_ids': [Command.set(self.env.ref('l10n_in.state_in_oc').ids)],
'country_id': country_in_id,
'note': _("SUPPLY MEANT FOR EXPORT/SUPPLY TO SEZ UNIT OR SEZ DEVELOPER FOR AUTHORISED OPERATIONS UNDER BOND OR LETTER OF UNDERTAKING WITHOUT PAYMENT OF INTEGRATED TAX."),
'tax_ids': self._get_l10n_in_fiscal_tax_vals('fiscal_position_in_lut_sez_1'),
},
'fiscal_position_in_lut_sez': {
'name': _("Export - LUT (WOP)"),
'sequence': 6,
'note': _('SUPPLY MEANT FOR EXPORT/SUPPLY TO SEZ UNIT OR SEZ DEVELOPER FOR AUTHORISED OPERATIONS UNDER BOND OR LETTER OF UNDERTAKING WITHOUT PAYMENT OF INTEGRATED TAX.'),
'tax_ids': self._get_l10n_in_fiscal_tax_vals('fiscal_position_in_lut_sez'),
},
}
def _get_l10n_in_fiscal_tax_vals(self, fiscal_position_xml_ids):
rates = [1, 2, 5, 12, 18, 28, 40]
taxes_xml_ids = []
if fiscal_position_xml_ids == 'fiscal_position_in_intra_state':
taxes_xml_ids = [f"sgst_{tax_type}_{rate}" for tax_type in ["sale", "purchase"] for rate in rates]
elif fiscal_position_xml_ids == 'fiscal_position_in_inter_state':
taxes_xml_ids = [f"igst_{tax_type}_{rate}" for tax_type in ["sale", "purchase"] for rate in rates]
elif fiscal_position_xml_ids == 'fiscal_position_in_export_sez_in':
taxes_xml_ids = [f"igst_sale_{rate}_sez_exp" for rate in rates] + [f"igst_purchase_{rate}" for rate in rates] + ['igst_sale_0_sez_exp']
elif fiscal_position_xml_ids == 'fiscal_position_in_lut_sez':
taxes_xml_ids = [f"igst_sale_{rate}_sez_exp_lut" for rate in rates] + ['igst_sale_0_sez_exp_lut']
elif fiscal_position_xml_ids == 'fiscal_position_in_lut_sez_1':
taxes_xml_ids = [f"igst_sale_{rate}_sez_lut" for rate in rates] + ['igst_sale_0_sez_lut']
return [Command.set(taxes_xml_ids)]
def _post_load_data(self, template_code, company, template_data):
super()._post_load_data(template_code, company, template_data)
if template_code == 'in':
company = company or self.env.company
company._update_l10n_in_is_gst_registered()
# The COA (Chart of Accounts) data is loaded after the initial compute methods are called.
# During initial journal setup, the payment methods and accounts may not exist yet,
# causing the payment method lines to not be properly configured.
# We call these helper methods again in _post_load_data to ensure all payment method lines
# are correctly assigned once all COA data is fully available.
bank_journals = company.bank_journal_ids
bank_journals._update_payment_method_lines("inbound")
bank_journals._update_payment_method_lines("outbound")

View file

@ -1,10 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class UoM(models.Model):
class UomUom(models.Model):
_inherit = "uom.uom"
# As per GST Rules you need to Specify UQC given by GST.

View file

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_message_tree_audit_log" model="ir.ui.view">
<field name="name">mail.message.tree.inherit.audit.log</field>
<field name="model">mail.message</field>
<field name="priority">99</field>
<field name="arch" type="xml">
<tree edit="0" delete="0" create="0" action="action_open_document" type="object">
<field name="res_id" invisible="1"/>
<field name="date"/>
<field name="author_id" widget="many2one_avatar"/>
<field name="l10n_in_audit_log_account_move_id"/>
<field name="l10n_in_audit_log_preview"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_message_tree_audit_log_search">
<field name="name">mail.message.search</field>
<field name="model">mail.message</field>
<field name="priority">99</field>
<field name="arch" type="xml">
<search string="Messages Search">
<field name="l10n_in_audit_log_account_move_id"/>
<field name="author_id"/>
<field name="date" string="Date"/>
<filter string="Update Only" name="update_only" domain="[('tracking_value_ids', '!=', False)]"/>
<group expand="0" string="Group By">
<filter string="date" name="group_by_date" domain="[]" context="{'group_by': 'date'}"/>
</group>
</search>
</field>
</record>
<record id="action_l10n_in_audit_trail_report" model="ir.actions.act_window">
<field name="name">Audit trail</field>
<field name="res_model">mail.message</field>
<field name="view_id" ref="view_message_tree_audit_log"/>
<field name="view_mode">tree</field>
<field name="domain">[
('model', '=', 'account.move'),
('message_type', '=', 'notification'),
('l10n_in_audit_log_account_move_id', '!=', False),
]</field>
<field name="search_view_id" ref="view_message_tree_audit_log_search"/>
</record>
<menuitem id="l10n_in_audit_trail_report_menu" name="Audit trail" action="action_l10n_in_audit_trail_report" parent="l10n_in.account_reports_in_statements_menu" sequence="2"
groups="account.group_account_readonly"/>
</odoo>

View file

@ -1,3 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_port_code_user,port.code.user,model_l10n_in_port_code,base.group_user,1,0,0,0
access_port_code_account_manager,port.code.user,model_l10n_in_port_code,account.group_account_manager,1,1,1,1
l10n_in.access_l10n_in_withhold_wizard,access_l10n_in_withhold_wizard,l10n_in.model_l10n_in_withhold_wizard,account.group_account_invoice,1,1,1,1
access_l10n_in_section_alert_account_readonly,l10n_in.section.alert.account.readonly,model_l10n_in_section_alert,account.group_account_readonly,1,0,0,0
access_l10n_in_section_alert_account_manager,l10n_in.section.alert.account.manager,model_l10n_in_section_alert,account.group_account_manager,1,1,1,1
access_l10n_in_pan_entity,l10n_in.pan.entity,model_l10n_in_pan_entity,base.group_user,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_port_code_user port.code.user model_l10n_in_port_code base.group_user 1 0 0 0
3 access_port_code_account_manager port.code.user model_l10n_in_port_code account.group_account_manager 1 1 1 1
4 l10n_in.access_l10n_in_withhold_wizard access_l10n_in_withhold_wizard l10n_in.model_l10n_in_withhold_wizard account.group_account_invoice 1 1 1 1
5 access_l10n_in_section_alert_account_readonly l10n_in.section.alert.account.readonly model_l10n_in_section_alert account.group_account_readonly 1 0 0 0
6 access_l10n_in_section_alert_account_manager l10n_in.section.alert.account.manager model_l10n_in_section_alert account.group_account_manager 1 1 1 1
7 access_l10n_in_pan_entity l10n_in.pan.entity model_l10n_in_pan_entity base.group_user 1 1 1 1

View file

@ -2,6 +2,5 @@
<odoo>
<record model="res.groups" id="group_l10n_in_reseller">
<field name="name">Manage Reseller(E-Commerce)</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
</odoo>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 60 KiB

View file

@ -0,0 +1,116 @@
import { AutoComplete } from "@web/core/autocomplete/autocomplete";
import { useChildRef } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { _t } from "@web/core/l10n/translation";
import { CharField, charField } from "@web/views/fields/char/char_field";
import { useInputField } from "@web/views/fields/input_field_hook";
const l10N_IN_HSN_SERVICE_URL = "https://services.gst.gov.in/commonservices/hsn/search/qsearch";
export class L10nInHsnAutoComplete extends CharField {
static template = "l10n_in.hsnAutoComplete";
static components = {
...CharField.components,
AutoComplete,
};
static props = {
...CharField.props,
l10nInHsnDescription: { type: String, optional: true },
};
setup() {
super.setup();
this.inputRef = useChildRef();
useInputField({
getValue: () => this.props.record.data[this.props.name] || "",
parse: (v) => this.parse(v),
ref: this.inputRef,
});
}
async getHsnSuggestions(value) {
const suggestions = [];
const onlyDigits = !isNaN(value) && value.indexOf(" ") < 0;
const params = [
{ type: "byCode", category: "null" }, // For code
{ type: "byDesc", category: "P" }, // For products
{ type: "byDesc", category: "S" }, // For services
];
const filteredParams = onlyDigits ? [params[0]] : params.slice(1);
try {
await Promise.all(
filteredParams.map(async (param) => {
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 5000);
const res = await fetch(
`${l10N_IN_HSN_SERVICE_URL}?inputText=${value}&selectedType=${param.type}&category=${param.category}`,
{ signal }
);
if (!res.ok) {
throw new Error(res.statusText);
}
const resData = await res.json();
for (const item of resData.data || []) {
if (item.c.length > 3) {
suggestions.push({
data: {
description: item.n,
},
label: item.c,
onSelect: () => this.selectSuggestion(item.c, item.n),
});
}
}
})
);
} catch (e) {
suggestions.push({
label: _t("Could not contact API"),
});
console.warn("HSN Autocomplete API error:", e);
}
return suggestions;
}
get sources() {
return [
{
options: async (request) => {
if (request?.length > 2) {
return await this.getHsnSuggestions(request);
} else {
return [];
}
},
optionSlot: "option",
placeholder: _t("Searching..."),
},
];
}
selectSuggestion(label, description) {
const data = { [this.props.name]: label };
if (this.props.l10nInHsnDescription) {
data[this.props.l10nInHsnDescription] = description;
}
setTimeout(() => this.props.record.update(data));
}
}
export const l10nInHsnAutoComplete = {
...charField,
component: L10nInHsnAutoComplete,
supportedOptions: [
{
label: _t("hsn description field"),
name: "hsn_description_field",
type: "string",
},
],
extractProps: ({ options }) => ({
l10nInHsnDescription: options.hsn_description_field,
}),
};
registry.category("fields").add("l10n_in_hsn_autocomplete", l10nInHsnAutoComplete);

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="l10n_in.hsnAutoComplete">
<AutoComplete
value="props.record.data[props.name] || ''"
sources="sources"
input="inputRef"
placeholder="props.placeholder || ''"
>
<t t-set-slot="option" t-slot-scope="optionScope">
<div class="text-wrap">
<strong t-out="optionScope.label"/>
<div t-out="optionScope.data.description"/>
</div>
</t>
</AutoComplete>
</t>
</templates>

View file

@ -0,0 +1,14 @@
import { patch } from "@web/core/utils/patch";
import { TestsSharedJsPython } from "@account/components/tests_shared_js_python/tests_shared_js_python";
import { accountTaxHelpers } from "@account/helpers/account_tax";
patch(TestsSharedJsPython.prototype, {
/** override **/
processTest(params){
if (params.test === "l10n_in_hsn_summary") {
const document = this.populateDocument(params.document);
return {'hsn': accountTaxHelpers.l10n_in_get_hsn_summary_table(document.lines, params.display_uom)};
}
return super.processTest(...arguments);
},
});

View file

@ -0,0 +1,99 @@
import { patch } from "@web/core/utils/patch";
import { accountTaxHelpers } from "@account/helpers/account_tax";
patch(accountTaxHelpers, {
/**
* [!] Mirror of the same method in account_tax.py.
* PLZ KEEP BOTH METHODS CONSISTENT WITH EACH OTHERS.
*/
l10n_in_get_hsn_summary_table(base_lines, display_uom) {
const l10n_in_gst_tax_types = new Set();
const items_map = {};
function get_base_line_grouping_key(base_line) {
const unique_taxes_data = new Set(
base_line.tax_details.taxes_data
.filter(tax_data => ['igst', 'cgst', 'sgst'].includes(tax_data.tax.l10n_in_gst_tax_type))
.map(tax_data => tax_data.tax)
);
const rate = [...unique_taxes_data].reduce((sum, tax) => sum + tax.amount, 0);
return {
l10n_in_hsn_code: base_line.l10n_in_hsn_code,
uom_name: base_line.product_uom_id.name,
rate: rate,
};
}
// quantity / amount_untaxed.
for (const base_line of base_lines) {
const raw_key = get_base_line_grouping_key(base_line);
if (!raw_key.l10n_in_hsn_code) {
continue;
}
const key = JSON.stringify(raw_key);
if (!(key in items_map)) {
items_map[key] = {
key: raw_key,
quantity: 0.0,
amount_untaxed: 0.0,
tax_amount_igst: 0.0,
tax_amount_cgst: 0.0,
tax_amount_sgst: 0.0,
tax_amount_cess: 0.0,
}
}
const item = items_map[key];
item.quantity += base_line.quantity;
item.amount_untaxed += (
base_line.tax_details.total_excluded_currency +
base_line.tax_details.delta_total_excluded_currency
);
}
// Tax amounts.
function grouping_function(base_line, tax_data) {
return tax_data ? {
...get_base_line_grouping_key(base_line),
l10n_in_gst_tax_type: tax_data.tax.l10n_in_gst_tax_type,
} : null;
}
const base_lines_aggregated_values = this.aggregate_base_lines_tax_details(base_lines, grouping_function);
const values_per_grouping_key = this.aggregate_base_lines_aggregated_values(base_lines_aggregated_values);
for (const values of Object.values(values_per_grouping_key)) {
const grouping_key = values.grouping_key;
if (!grouping_key || !grouping_key.l10n_in_hsn_code || !grouping_key.l10n_in_gst_tax_type) {
continue;
}
const key = JSON.stringify({
l10n_in_hsn_code: grouping_key.l10n_in_hsn_code,
uom_name: grouping_key.uom_name,
rate: grouping_key.rate,
});
const item = items_map[key];
const l10n_in_gst_tax_type = grouping_key.l10n_in_gst_tax_type;
item[`tax_amount_${l10n_in_gst_tax_type}`] += values.tax_amount_currency;
l10n_in_gst_tax_types.add(l10n_in_gst_tax_type);
}
const items = [];
for (const values of Object.values(items_map)) {
const item = {...values.key, ...values};
delete item.key;
items.push(item);
}
return {
has_igst: l10n_in_gst_tax_types.has("igst"),
has_gst: l10n_in_gst_tax_types.has("cgst") || l10n_in_gst_tax_types.has("sgst"),
has_cess: l10n_in_gst_tax_types.has("cess"),
nb_columns: 5 + l10n_in_gst_tax_types.size,
display_uom: display_uom,
items: items,
};
}
});

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="800" width="1200" class="main-header__logo-image" fill="#A1A1A1" viewBox="-65.39955 -43.28375 566.7961 259.7025"><path fill="#5f6368" d="M206.197 84.585v50.75h-16.1V10.005h42.7a38.61 38.61 0 0127.65 10.85 34.88 34.88 0 0111.55 26.45 34.72 34.72 0 01-11.55 26.6q-11.2 10.68-27.65 10.67h-26.6zm0-59.15v43.75h27a21.28 21.28 0 0015.93-6.48 21.36 21.36 0 000-30.63 21 21 0 00-15.93-6.65h-27zm102.9 21.35q17.85 0 28.18 9.54 10.33 9.54 10.32 26.16v52.85h-15.4v-11.9h-.7q-10 14.7-26.6 14.7-14.17 0-23.71-8.4a26.82 26.82 0 01-9.54-21q0-13.31 10.06-21.17 10.06-7.86 26.86-7.88 14.34 0 23.62 5.25v-3.68a18.33 18.33 0 00-6.65-14.25 22.8 22.8 0 00-15.54-5.87q-13.49 0-21.35 11.38l-14.18-8.93q11.7-16.8 34.63-16.8zm-20.83 62.3a12.86 12.86 0 005.34 10.5 19.64 19.64 0 0012.51 4.2 25.67 25.67 0 0018.11-7.52q8-7.53 8-17.67-7.53-6-21-6-9.81 0-16.36 4.73c-4.41 3.2-6.6 7.09-6.6 11.76zm147.73-59.5l-53.76 123.55h-16.62l19.95-43.23-35.35-80.32h17.5l25.55 61.6h.35l24.85-61.6z"/><path fill="#4285f4" d="M141.137 73.645a85.79 85.79 0 00-1.24-14.64h-67.9v27.73h38.89a33.33 33.33 0 01-14.38 21.88v18h23.21c13.59-12.53 21.42-31.06 21.42-52.97z"/><path fill="#34a853" d="M71.997 144.005c19.43 0 35.79-6.38 47.72-17.38l-23.21-18c-6.46 4.38-14.78 6.88-24.51 6.88-18.78 0-34.72-12.66-40.42-29.72H7.667v18.55a72 72 0 0064.33 39.67z"/><path fill="#fbbc04" d="M31.577 85.785a43.14 43.14 0 010-27.56v-18.55H7.667a72 72 0 000 64.66z"/><path fill="#ea4335" d="M71.997 28.505a39.09 39.09 0 0127.62 10.8l20.55-20.55A69.18 69.18 0 0071.997.005a72 72 0 00-64.33 39.67l23.91 18.55c5.7-17.06 21.64-29.72 40.42-29.72z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="800" width="1200" viewBox="-2.5242255 -1.318595 21.876621 7.91157"><g fill="none"><path d="M16.77137 1.55572c-.15275-.43674-.56903-.75036-1.05798-.75036h-.01023c-.31785 0-.6043.13229-.80821.34466-.20426-.21237-.49072-.34466-.80822-.34466h-.01023c-.2794 0-.5348.1023-.73095.27164V.99092c-.0067-.08573-.07726-.1531-.1644-.1531h-.75c-.09172 0-.1658.07408-.1658.16615v4.07282c0 .09207.07408.16616.1658.16616h.75c.08361 0 .1524-.06244.16334-.14323l-.00035-2.92382a.26293.26293 0 01.0014-.02928c.012-.13053.1076-.23777.2586-.25118h.13827c.06315.00564.11642.02787.1584.06138.06526.05186.1016.13159.1016.21908l.00282 2.90936c0 .09207.07443.1665.1658.1665h.75c.08856 0 .16052-.07055.16476-.15839l-.00035-2.92135c-.00036-.09596.0441-.18274.12206-.23425.03845-.0247.08467-.04127.13793-.04621h.13829c.16228.01411.26035.13723.26.28046l.00282 2.90548c0 .09207.07444.16615.1658.16615h.75001c.09137 0 .1658-.07408.1658-.16615V1.95259c0-.21308-.02398-.30374-.05679-.39687M11.69398.84843h-.42897V.15134C11.265.06773 11.19727 0 11.11366 0c-.00988 0-.0194.0014-.02857.00317-.47555.13053-.3803.78917-1.24848.84526h-.08432c-.0127 0-.02469.00176-.03633.00423h-.0007l.0007.00035c-.07409.01659-.12982.0822-.12982.16123v.75c0 .09137.07443.1658.16615.1658h.45262l-.0007 3.1803c0 .09066.07337.16404.16403.16404h.74154c.09031 0 .1637-.07338.1637-.16404l.00034-3.1803h.42016c.09137 0 .1658-.07443.1658-.1658v-.75c0-.09137-.07443-.1658-.1658-.1658" fill="#00BAF2"/><path d="M8.99555.84843h-.75c-.09137 0-.16546.07444-.16546.1658v1.55082c-.00176.09595-.07937.17286-.17568.17286h-.31397c-.09737 0-.17604-.07832-.17604-.17569l-.00282-1.54798c0-.09137-.07444-.1658-.1658-.1658h-.75001c-.09172 0-.1658.07443-.1658.1658v1.69968c0 .64558.46037 1.10596 1.1063 1.10596 0 0 .48472 0 .49954.00282.08749.00988.15557.08325.15557.17356 0 .08926-.06667.16228-.1531.17322-.00423.0007-.00811.00176-.0127.00247l-1.09679.00388c-.09172 0-.1658.07443-.1658.1658v.74966c0 .09172.07408.1658.1658.1658h1.22626c.64628 0 1.1063-.46002 1.1063-1.10595v-3.1369c0-.09137-.07408-.1658-.1658-.1658M1.73602 2.22268v.46285c0 .097-.07867.17603-.17568.17603l-.4759.00035v-.92745h.4759c.09701 0 .17568.07831.17568.17568zM1.80199.84825H.16263C.07267.84825 0 .92128 0 1.01088v.73484c0 .00141.00035.00282.00035.00423 0 .00353-.00035.00706-.00035.01023v3.32599c0 .09031.06773.16405.1517.16616h.7641c.09137 0 .1658-.07408.1658-.1658l.00283-1.13983h.71755c.60042 0 1.01882-.41663 1.01882-1.01953V1.8692c0-.6029-.4184-1.02094-1.01882-1.02094zM4.8479 3.98346v.11712c0 .00953-.0014.0187-.00281.02752a.17498.17498 0 01-.00706.02434c-.02329.06562-.0889.11324-.16687.11324h-.3122c-.09737 0-.17674-.07408-.17674-.1651v-.14146c0-.00176-.00036-.00353-.00036-.00529l.00036-.37641v-.11784l.00035-.00105c.00035-.09067.07902-.16404.17639-.16404h.3122c.09772 0 .17675.07373.17675.1651zM4.72868.85256h-1.0407c-.09207 0-.1665.06985-.1665.15557v.29175c0 .00176.00035.00388.00035.00564 0 .00212-.00036.00423-.00036.00635v.3997c0 .09067.07903.16475.1764.16475h.99094c.07832.01235.14041.0695.14923.15875v.09666c-.00882.08502-.0702.1471-.145.15416h-.4907c-.65264 0-1.1176.43357-1.1176 1.04246v.87207c0 .60537.3997 1.0361 1.04774 1.0361h1.35996c.24412 0 .44203-.18485.44203-.41239V1.97827c0-.69003-.3556-1.12571-1.2058-1.12571z" fill="#1F336B"/></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="800" width="1200" xml:space="preserve" y="0" x="0" id="Layer_2" version="1.1" viewBox="-19.77372 -8.949675 171.37224 53.69805"><style id="style28" type="text/css">.st0{fill:#5f259f}</style><g transform="translate(.0248 -6.071)" id="g34"><circle r="17.9" id="ellipse30" cy="24" cx="17.9" class="st0" transform="matrix(.2298 -.9732 .9732 .2298 -9.5957 35.8754)"/><path id="path32" d="M90.5 34.2v-6.5c0-1.6-.6-2.4-2.1-2.4-.6 0-1.3.1-1.7.2V35c0 .3-.3.6-.6.6h-2.3c-.3 0-.6-.3-.6-.6V23.9c0-.4.3-.7.6-.8 1.5-.5 3-.8 4.6-.8 3.6 0 5.6 1.9 5.6 5.4v7.4c0 .3-.3.6-.6.6H92c-.9 0-1.5-.7-1.5-1.5zm9-3.9l-.1.9c0 1.2.8 1.9 2.1 1.9 1 0 1.9-.3 2.9-.8.1 0 .2-.1.3-.1.2 0 .3.1.4.2.1.1.3.4.3.4.2.3.4.7.4 1 0 .5-.3 1-.7 1.2-1.1.6-2.4.9-3.8.9-1.6 0-2.9-.4-3.9-1.2-1-.9-1.6-2.1-1.6-3.6v-3.9c0-3.1 2-5 5.4-5 3.3 0 5.2 1.8 5.2 5v2.4c0 .3-.3.6-.6.6h-6.3zm-.1-2.2h3.8v-1c0-1.2-.7-2-1.9-2s-1.9.7-1.9 2zm25.5 2.2l-.1.9c0 1.2.8 1.9 2.1 1.9 1 0 1.9-.3 2.9-.8.1 0 .2-.1.3-.1.2 0 .3.1.4.2.1.1.3.4.3.4.2.3.4.7.4 1 0 .5-.3 1-.7 1.2-1.1.6-2.4.9-3.8.9-1.6 0-2.9-.4-3.9-1.2-1-.9-1.6-2.1-1.6-3.6v-3.9c0-3.1 2-5 5.4-5 3.3 0 5.2 1.8 5.2 5v2.4c0 .3-.3.6-.6.6h-6.3zm-.1-2.2h3.8v-1c0-1.2-.7-2-1.9-2s-1.9.7-1.9 2zM66 35.7h1.4c.3 0 .6-.3.6-.6v-7.4c0-3.4-1.8-5.4-4.8-5.4-.9 0-1.9.2-2.5.4V19c0-.8-.7-1.5-1.5-1.5h-1.4c-.3 0-.6.3-.6.6v17c0 .3.3.6.6.6h2.3c.3 0 .6-.3.6-.6v-9.4c.5-.2 1.2-.3 1.7-.3 1.5 0 2.1.7 2.1 2.4v6.5c.1.7.7 1.4 1.5 1.4zm15.1-8.4V31c0 3.1-2.1 5-5.6 5-3.4 0-5.6-1.9-5.6-5v-3.7c0-3.1 2.1-5 5.6-5 3.5 0 5.6 1.9 5.6 5zm-3.5 0c0-1.2-.7-2-2-2s-2 .7-2 2V31c0 1.2.7 1.9 2 1.9s2-.7 2-1.9zm-22.3-1.7c0 3.2-2.4 5.4-5.6 5.4-.8 0-1.5-.1-2.2-.4v4.5c0 .3-.3.6-.6.6h-2.3c-.3 0-.6-.3-.6-.6V19.2c0-.4.3-.7.6-.8 1.5-.5 3-.8 4.6-.8 3.6 0 6.1 2.2 6.1 5.6zM51.7 23c0-1.6-1.1-2.4-2.6-2.4-.9 0-1.5.3-1.5.3v6.6c.6.3.9.4 1.6.4 1.5 0 2.6-.9 2.6-2.4V23zm68.2 2.6c0 3.2-2.4 5.4-5.6 5.4-.8 0-1.5-.1-2.2-.4v4.5c0 .3-.3.6-.6.6h-2.3c-.3 0-.6-.3-.6-.6V19.2c0-.4.3-.7.6-.8 1.5-.5 3-.8 4.6-.8 3.6 0 6.1 2.2 6.1 5.6zm-3.6-2.6c0-1.6-1.1-2.4-2.6-2.4-.9 0-1.5.3-1.5.3v6.6c.6.3.9.4 1.6.4 1.5 0 2.6-.9 2.6-2.4V23z" class="st0"/></g><path id="path36" d="M26.0248 13.229c0-.7-.6-1.3-1.3-1.3h-2.4l-5.5-6.3c-.5-.6-1.3-.8-2.1-.6l-1.9.6c-.3.1-.4.5-.2.7l6 5.7h-9.1c-.3 0-.5.2-.5.5v1c0 .7.6 1.3 1.3 1.3h1.4v4.8c0 3.6 1.9 5.7 5.1 5.7 1 0 1.8-.1 2.8-.5v3.2c0 .9.7 1.6 1.6 1.6h1.4c.3 0 .6-.3.6-.6v-14.3h2.3c.3 0 .5-.2.5-.5zm-6.4 8.6c-.6.3-1.4.4-2 .4-1.6 0-2.4-.8-2.4-2.6v-4.8h4.4z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="60" fill-rule="evenodd"><path d="M95.678 42.9L110 29.835l-6.784-13.516z" fill="#097939"/><path d="M90.854 42.9l14.322-13.065-6.784-13.516z" fill="#ed752e"/><path d="M22.41 16.47l-6.03 21.475 21.407.15 5.88-21.625h5.427l-7.05 25.14c-.27.96-1.298 1.74-2.295 1.74H12.31c-1.664 0-2.65-1.3-2.2-2.9l6.724-23.98zm66.182-.15h5.427l-7.538 27.03h-5.58zM49.698 27.582l27.136-.15 1.81-5.707H51.054l1.658-5.256 29.4-.27c1.83-.017 2.92 1.4 2.438 3.167L81.78 29.49c-.483 1.766-2.36 3.197-4.19 3.197H53.316L50.454 43.8h-5.28z" fill="#747474"/></svg>

After

Width:  |  Height:  |  Size: 593 B

View file

@ -0,0 +1,8 @@
from . import common
from . import test_hsn_summary
from . import test_partner_details_on_invoice
from . import test_l10n_in_fiscal_position
from . import test_check_status
from . import test_tds_tcs_alert
from . import test_gstr_section
from . import test_invoice_label

View file

@ -0,0 +1,271 @@
from datetime import date
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo import Command
class L10nInTestInvoicingCommon(AccountTestInvoicingCommon):
@classmethod
@AccountTestInvoicingCommon.setup_country('in')
def setUpClass(cls):
super().setUpClass()
cls.maxDiff = None
cls.test_date = date(2023, 5, 20)
# === Countries === #
cls.country_in = cls.env.ref('base.in')
cls.country_us = cls.env.ref('base.us')
# === States === #
cls.state_in_gj = cls.env.ref('base.state_in_gj')
cls.state_in_mh = cls.env.ref('base.state_in_mh')
cls.state_in_hp = cls.env.ref('base.state_in_hp')
# === Companies === #
cls.default_company = cls.company_data['company']
cls.default_company.write({
'name': "Default Company",
'state_id': cls.state_in_gj.id,
'vat': "24AAGCC7144L6ZE",
'street': "Khodiyar Chowk",
'street2': "Sala Number 3",
'city': "Amreli",
'zip': "365220",
'l10n_in_is_gst_registered': True,
'l10n_in_tds_feature': True,
'l10n_in_tcs_feature': True,
})
cls.outside_in_company = cls.env['res.company'].create({
'name': 'Outside India Company',
'country_id': cls.country_us.id,
})
cls.user.write({
'company_ids': [cls.default_company.id, cls.outside_in_company.id],
'company_id': cls.default_company.id,
})
# === Partners === #
cls.partner_a.write({
'name': "Partner Intra State",
'vat': '24ABCPM8965E1ZE',
'state_id': cls.default_company.state_id.id,
'country_id': cls.country_in.id,
'street': "Karansinhji Rd",
'street2': "Karanpara",
'city': "Rajkot",
'zip': "360001",
})
cls.partner_b.write({
'vat': '27DJMPM8965E1ZE',
'state_id': cls.state_in_mh.id,
'country_id': cls.country_in.id,
'street': "Sangeet Samrat Naushad Ali Rd",
'city': "Mumbai",
'zip': "400052",
})
cls.partner_foreign = cls.env['res.partner'].create({
'name': "Foreign Partner",
'country_id': cls.country_us.id,
'state_id': cls.env.ref("base.state_us_1").id,
'street': "351 Horner Chapel Rd",
'city': "Peebles",
'zip': "45660",
})
cls.partner_foreign_no_state = cls.env['res.partner'].create({
'name': "Foreign Partner Without State",
'country_id': cls.country_us.id,
# No state_id defined
})
cls.sez_partner = cls.env['res.partner'].create({
'name': 'SEZ Partner',
'vat': '36AAAAA1234AAZA',
'l10n_in_gst_treatment': 'special_economic_zone',
'street': 'Block no. 402',
'city': 'Some city',
'zip': '500002',
'state_id': cls.env.ref('base.state_in_gj').id,
'country_id': cls.env.ref('base.in').id,
})
# === Taxes === #
AccountChartTemplate = cls.env['account.chart.template']
cls.sgst_sale_5 = AccountChartTemplate.ref('sgst_sale_5')
cls.sgst_purchase_5 = AccountChartTemplate.ref('sgst_purchase_5')
cls.igst_sale_5 = AccountChartTemplate.ref('igst_sale_5')
cls.igst_sale_18 = AccountChartTemplate.ref('igst_sale_18')
cls.sgst_sale_18 = AccountChartTemplate.ref('sgst_sale_18')
cls.igst_sale_18_rcm = AccountChartTemplate.ref('igst_sale_18_rc')
cls.igst_sale_18_sez_lut = AccountChartTemplate.ref('igst_sale_18_sez_lut')
cls.igst_sale_18_sez_exp_lut = AccountChartTemplate.ref('igst_sale_18_sez_exp_lut')
cls.igst_sale_18_sez_exp = AccountChartTemplate.ref('igst_sale_18_sez_exp')
cls.igst_sale_18_sez_exp_inc = cls.igst_sale_18_sez_exp.copy({'price_include_override': 'tax_included'})
cls.gst_with_cess = (
AccountChartTemplate.ref("sgst_sale_12")
+ AccountChartTemplate.ref("cess_5_plus_1591_sale")
)
cls.exempt = AccountChartTemplate.ref('exempt_sale')
# === Products === #
cls.product_a.write({
"l10n_in_hsn_code": "111111",
'taxes_id': cls.sgst_sale_5,
'supplier_taxes_id': cls.sgst_purchase_5,
})
cls.product_b.write({
"l10n_in_hsn_code": "111111",
'uom_id': cls.env.ref('uom.product_uom_unit').id,
'lst_price': 1000.0,
'standard_price': 1000.0,
'taxes_id': cls.sgst_sale_5.ids,
'supplier_taxes_id': cls.sgst_purchase_5.ids,
})
cls.product_with_cess = cls.env["product.product"].create({
"name": "product_with_cess",
"uom_id": cls.env.ref("uom.product_uom_unit").id,
"lst_price": 1000.0,
"standard_price": 800.0,
"property_account_income_id": cls.company_data["default_account_revenue"].id,
"property_account_expense_id": cls.company_data["default_account_expense"].id,
"taxes_id": [Command.set(cls.gst_with_cess.ids)],
"supplier_taxes_id": [Command.set(cls.sgst_purchase_5.ids)],
"l10n_in_hsn_code": "333333",
})
# === Fiscal Positions === #
cls.fp_in_intra_state = cls.env["account.chart.template"].ref('fiscal_position_in_intra_state')
cls.fp_in_inter_state = cls.env["account.chart.template"].ref('fiscal_position_in_inter_state')
cls.fp_in_export = cls.env["account.chart.template"].ref('fiscal_position_in_export_sez_in')
# === Invoices === #
cls.invoice_a = cls.init_invoice(
move_type='out_invoice',
partner=cls.partner_a,
amounts=[110, 500],
taxes=cls.igst_sale_18,
)
cls.invoice_b = cls.init_invoice(
move_type='out_invoice',
partner=cls.partner_b,
amounts=[250, 600],
taxes=cls.igst_sale_18,
)
cls.invoice_c = cls.init_invoice(
move_type='out_invoice',
partner=cls.partner_foreign,
amounts=[300, 740],
taxes=cls.igst_sale_18,
)
cls.invoice_d = cls.init_invoice(
move_type='out_invoice',
partner=cls.partner_foreign_no_state,
amounts=[100, 200],
taxes=cls.igst_sale_18,
)
cls.invoice_with_rcm = cls.init_invoice(
"out_invoice",
partner=cls.partner_b,
products=cls.product_a,
taxes=cls.igst_sale_18_rcm,
)
cls.invoice_with_sez_lut = cls.init_invoice(
"out_invoice",
partner=cls.sez_partner,
products=cls.product_a,
taxes=cls.igst_sale_18_sez_lut,
)
cls.invoice_with_sez_without_lut = cls.init_invoice(
"out_invoice",
partner=cls.sez_partner,
products=cls.product_a,
taxes=cls.igst_sale_18,
)
cls.invoice_with_export_lut = cls.init_invoice(
"out_invoice",
partner=cls.partner_foreign,
products=cls.product_a,
taxes=cls.igst_sale_18_sez_exp_lut,
)
cls.invoice_with_export_without_lut = cls.init_invoice(
"out_invoice",
partner=cls.partner_foreign,
products=cls.product_a,
taxes=cls.igst_sale_18_sez_exp,
)
cls.invoice_with_export_without_lut_inc = cls.init_invoice(
"out_invoice",
partner=cls.partner_foreign,
products=cls.product_a,
taxes=cls.igst_sale_18_sez_exp_inc,
)
@classmethod
def _set_vals_and_post(cls, move, ref=None, line_vals=None, post=True, irn=None):
if ref:
move.ref = ref
if irn:
move.l10n_in_irn_number = irn
if line_vals:
move.write({'invoice_line_ids': [Command.update(line.id, line_vals) for line in move.line_ids]})
if post:
move.action_post()
return move
@classmethod
def _init_inv(cls, move_type='out_invoice', company=None, ref=None, partner=None, taxes=None, invoice_date=None, products=None, line_vals=None, post=True, irn=None):
return cls._set_vals_and_post(
move=cls.init_invoice(
move_type,
products=products or cls.product_a,
invoice_date=invoice_date or cls.test_date,
taxes=taxes,
company=company or cls.default_company,
partner=partner,
),
ref=ref,
irn=irn,
line_vals=line_vals,
post=post
)
@classmethod
def _create_credit_note(cls, inv, ref=None, credit_note_date=None, line_vals=None, post=True):
move = inv._reverse_moves()
move.invoice_date = credit_note_date or cls.test_date
return cls._set_vals_and_post(
move=move,
ref=ref,
line_vals=line_vals,
post=post
)
@classmethod
def _create_debit_note(cls, inv, ref=None, debit_note_date=None, line_vals=None):
move_debit_note_wiz = cls.env['account.debit.note'].with_context(
active_model="account.move",
active_ids=inv.ids
).create({
'date': debit_note_date or cls.test_date,
'reason': 'no reason',
'copy_lines': True,
})
move_debit_note_wiz.create_debit()
return cls._set_vals_and_post(move=inv.debit_note_ids[0], ref=ref, line_vals=line_vals)

View file

@ -0,0 +1,87 @@
from unittest.mock import patch
from freezegun import freeze_time
from odoo.addons.l10n_in.models.iap_account import IapAccount
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import UserError
from odoo.tools import mute_logger
from datetime import date
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestGSTStatusFeature(TransactionCase):
def setUp(self):
self.partner1 = self.env["res.partner"].create(
{"name": "Active GSTIN", "vat": "36AAACM4154G1ZO"}
)
self.partner2 = self.env["res.partner"].create(
{"name": "Cancelled GSTIN", "vat": "19AABCT1332L2ZD"}
)
self.partner3 = self.env["res.partner"].create(
{"name": "Invalid GSTIN", "vat": "19AACCT6304M1ZB"}
)
self.partner4 = self.env["res.partner"].create(
{"name": "No Records GSTIN", "vat": "19AACCT6304M1DB"}
)
self.partner5 = self.env["res.partner"].create(
{
"name": "Partner Vat Reset",
"vat": "36AAACM4154G1ZO",
"country_id": self.env.ref('base.in').id,
"l10n_in_gstin_verified_status": "active",
"l10n_in_gstin_verified_date": "2024-06-01",
}
)
self.mock_responses = {
"active": {
"data": {"sts": "Active"}
},
"cancelled": {
"data": {"sts": "Cancelled"}
},
"invalid": {
"error": [{"code": "SWEB_9035", "message": "Invalid GSTIN / UID"}],
},
"no_records": {
"error": [{"code": "FO8000", "message": "No records found for the provided GSTIN."}],
},
}
self.env.company.l10n_in_gstin_status_feature = True
self.env.company.account_fiscal_country_id = self.env.ref("base.in")
@freeze_time('2024-05-20')
@mute_logger('odoo.addons.l10n_in.models.res_partner')
def check_gstin_status(self, partner, expected_status, mock_response, raises_exception=False):
with patch.object(IapAccount, "_l10n_in_connect_to_server", return_value=mock_response):
if raises_exception:
with self.assertRaises(UserError):
partner.action_l10n_in_verify_gstin_status()
else:
partner.action_l10n_in_verify_gstin_status()
self.assertEqual(partner.l10n_in_gstin_verified_status, expected_status)
self.assertEqual(partner.l10n_in_gstin_verified_date, date(2024, 5, 20))
def test_gstin_status(self):
"""Test GSTIN status for various cases"""
self.check_gstin_status(
self.partner1,
expected_status=True,
mock_response=self.mock_responses["active"]
)
self.check_gstin_status(
self.partner2,
expected_status=False,
mock_response=self.mock_responses["cancelled"]
)
self.check_gstin_status(
self.partner3,
expected_status=False,
raises_exception=True,
mock_response=self.mock_responses["invalid"],
)
self.check_gstin_status(
self.partner4,
expected_status=False,
raises_exception=True,
mock_response=self.mock_responses["no_records"],
)

View file

@ -0,0 +1,124 @@
from datetime import date
from odoo import Command
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
from odoo.tests import tagged
TEST_DATE = date(2025, 6, 8)
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestGstrSection(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_b.l10n_in_gst_treatment = "regular"
cls.partner_foreign.l10n_in_gst_treatment = 'overseas'
cls.sez_partner = cls.partner_a.copy({"l10n_in_gst_treatment": "special_economic_zone"})
cls.large_unregistered_partner = cls.partner_a.copy({"state_id": cls.state_in_mh.id, "vat": None, "l10n_in_gst_treatment": "unregistered"})
ChartTemplate = cls.env['account.chart.template']
cls.nil_rated_tax = ChartTemplate.ref('nil_rated_sale')
cls.igst_lut_sale_28_sez = ChartTemplate.ref('igst_sale_28_sez_lut')
cls.igst_rc_sale_18 = ChartTemplate.ref('igst_sale_18_rc')
cls.igst_exp_sale_18 = ChartTemplate.ref('igst_sale_18_sez_exp')
def test_gstr_sections(self):
def assert_line_sections(lines, expected_sections):
for line in lines.filtered(lambda l: l.display_type in ('product', 'tax')):
if line.display_type == 'product':
tax_types = {tax.l10n_in_tax_type for tax in line.tax_ids}
matched = tax_types & expected_sections.keys()
if matched:
expected = expected_sections[matched.pop()]
else:
expected = expected_sections.get('with_gst_tag')
else: # tax line
expected = expected_sections['with_gst_tag'] if line.tax_tag_ids else expected_sections['no_tag']
self.assertEqual(line.l10n_in_gstr_section, expected)
# SEZ without payment with Nil Rated
sez_invoice = self._init_inv(
partner=self.sez_partner,
taxes=self.igst_lut_sale_28_sez,
line_vals={'price_unit': 1000, 'quantity': 1},
post=False,
invoice_date=TEST_DATE,
)
sez_invoice.write({
'invoice_line_ids': [Command.create({
'product_id': self.product_b.id,
'account_id': sez_invoice.invoice_line_ids[0].account_id.id,
'price_unit': 500,
'quantity': 1,
'tax_ids': [(6, 0, [self.nil_rated_tax.id])],
})],
})
sez_invoice.action_post()
assert_line_sections(sez_invoice.line_ids, {
'with_gst_tag': 'sale_sez_wop',
'nil_rated': 'sale_nil_rated',
'no_tag': 'sale_out_of_scope',
})
sez_credit_note = self._create_credit_note(inv=sez_invoice)
assert_line_sections(sez_credit_note.line_ids, {
'with_gst_tag': 'sale_cdnr_sez_wop',
'nil_rated': 'sale_nil_rated',
'no_tag': 'sale_out_of_scope',
})
# Export with payment
exp_invoice = self._init_inv(
partner=self.partner_foreign,
taxes=self.igst_exp_sale_18,
line_vals={'price_unit': 3000, 'quantity': 1},
invoice_date=TEST_DATE,
)
assert_line_sections(exp_invoice.line_ids, {
'with_gst_tag': 'sale_exp_wp',
'no_tag': 'sale_out_of_scope',
})
exp_credit_note = self._create_credit_note(inv=exp_invoice)
assert_line_sections(exp_credit_note.line_ids, {
'with_gst_tag': 'sale_cdnur_exp_wp',
'no_tag': 'sale_out_of_scope',
})
# B2B RCM
b2b_rcm_invoice = self._init_inv(
partner=self.partner_b,
taxes=self.igst_rc_sale_18,
line_vals={'price_unit': 1000, 'quantity': 1},
invoice_date=TEST_DATE,
)
assert_line_sections(b2b_rcm_invoice.line_ids, {
'with_gst_tag': 'sale_b2b_rcm',
'no_tag': 'sale_out_of_scope',
})
b2b_rcm_credit_note = self._create_credit_note(inv=b2b_rcm_invoice)
assert_line_sections(b2b_rcm_credit_note.line_ids, {
'with_gst_tag': 'sale_cdnr_rcm',
'no_tag': 'sale_out_of_scope',
})
# B2CL
b2cl_invoice = self._init_inv(
partner=self.large_unregistered_partner,
taxes=self.igst_sale_18,
line_vals={'price_unit': 220000, 'quantity': 1},
invoice_date=TEST_DATE,
)
assert_line_sections(b2cl_invoice.line_ids, {
'with_gst_tag': 'sale_b2cl',
'no_tag': 'sale_out_of_scope',
})
b2cl_credit_note = self._create_credit_note(inv=b2cl_invoice, line_vals={'quantity': 0.5})
assert_line_sections(b2cl_credit_note.line_ids, {
'with_gst_tag': 'sale_cdnur_b2cl',
'no_tag': 'sale_out_of_scope',
})

View file

@ -0,0 +1,770 @@
from odoo import Command
from odoo.addons.account.tests.common import TestTaxCommon
from odoo.tests import tagged
@tagged('post_install', '-at_install', 'post_install_l10n')
class TestL10nInHSNSummary(TestTaxCommon):
@classmethod
@TestTaxCommon.setup_country('in')
def setUpClass(cls):
super().setUpClass()
cls.company_data['company'].write({
'l10n_in_is_gst_registered': True,
})
cls.test_hsn_code_1 = '1234'
cls.test_hsn_code_2 = '4321'
cls.uom_unit = cls.env.ref('uom.product_uom_unit')
cls.uom_dozen = cls.env.ref('uom.product_uom_dozen')
cls.product_a.l10n_in_hsn_code = cls.test_hsn_code_1
cls.product_b.l10n_in_hsn_code = cls.test_hsn_code_2
cls.product_c = cls.env['product.product'].create({
'name': 'product_c',
'l10n_in_hsn_code': cls.test_hsn_code_1,
'uom_id': cls.env.ref('uom.product_uom_unit').id,
'lst_price': 1000.0,
'property_account_income_id': cls.company_data['default_account_revenue'].id,
})
ChartTemplate = cls.env['account.chart.template']
cls.gst_5 = ChartTemplate.ref('sgst_sale_5')
cls.gst_18 = ChartTemplate.ref('sgst_sale_18')
cls.igst_5 = ChartTemplate.ref('igst_sale_5')
cls.igst_18 = ChartTemplate.ref('igst_sale_18')
cls.cess_5_plus_1591 = ChartTemplate.ref('cess_5_plus_1591_sale')
cls.nil_rated = ChartTemplate.ref('nil_rated_sale')
cls.exempt_0 = ChartTemplate.ref('exempt_sale')
cls.igst_18_rc = ChartTemplate.ref('igst_sale_18_rc')
def _jsonify_tax(self, tax):
# EXTENDS 'account.
values = super()._jsonify_tax(tax)
values['l10n_in_gst_tax_type'] = tax.l10n_in_gst_tax_type
return values
def _jsonify_document_line(self, document, index, line):
# EXTENDS 'account.
values = super()._jsonify_document_line(document, index, line)
values['l10n_in_hsn_code'] = line['l10n_in_hsn_code']
return values
def convert_base_line_to_invoice_line(self, document, base_line):
# EXTENDS 'account.
values = super().convert_base_line_to_invoice_line(document, base_line)
values['l10n_in_hsn_code'] = base_line['l10n_in_hsn_code']
return values
# -------------------------------------------------------------------------
# l10n_in_hsn_summary
# -------------------------------------------------------------------------
def _assert_sub_test_l10n_in_hsn_summary(self, results, expected_values):
self.assertEqual(
{k: len(v) if k == 'items' else v for k, v in results['hsn'].items()},
{k: len(v) if k == 'items' else v for k, v in expected_values.items()},
)
self.assertEqual(len(results['hsn']['items']), len(expected_values['items']))
for item, expected_item in zip(results['hsn']['items'], expected_values['items']):
self.assertDictEqual(item, expected_item)
def _create_py_sub_test_l10n_in_hsn_summary(self, document, display_uom):
return {
'hsn': self.env['account.tax']._l10n_in_get_hsn_summary_table(document['lines'], display_uom),
}
def _create_js_sub_test_l10n_in_hsn_summary(self, document, display_uom):
return {
'test': 'l10n_in_hsn_summary',
'document': self._jsonify_document(document),
'display_uom': display_uom,
}
def assert_l10n_in_hsn_summary(
self,
document,
expected_values,
display_uom=False,
):
self._create_assert_test(
expected_values,
self._create_py_sub_test_l10n_in_hsn_summary,
self._create_js_sub_test_l10n_in_hsn_summary,
self._assert_sub_test_l10n_in_hsn_summary,
document,
display_uom,
)
# -------------------------------------------------------------------------
# invoice l10n_in_hsn_summary
# -------------------------------------------------------------------------
def assert_invoice_l10n_in_hsn_summary(self, invoice, expected_values):
results = {'hsn': {
**invoice._l10n_in_get_hsn_summary_table(),
# 'display_uom' is just checking if the user has the uom group. It's irrelevant to test it.
'display_uom': expected_values['display_uom'],
}}
self._assert_sub_test_l10n_in_hsn_summary(results, expected_values)
# -------------------------------------------------------------------------
# Tests
# -------------------------------------------------------------------------
def _test_l10n_in_hsn_summary_1(self):
""" Test GST/IGST taxes. """
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': False,
'has_gst': True,
'has_cess': False,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 2300.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 57.5,
'tax_amount_sgst': 57.5,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 2300.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 207.0,
'tax_amount_sgst': 207.0,
'tax_amount_cess': 0.0,
},
],
}
yield 1, document, expected_values
# Another UOM on the second line.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 12000.0, 'product_uom_id': self.uom_dozen, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': False,
'has_gst': True,
'has_cess': False,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 7.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 1700.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 42.5,
'tax_amount_sgst': 42.5,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_dozen.name,
'rate': 5.0,
'amount_untaxed': 12000.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 300.0,
'tax_amount_sgst': 300.0,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 2300.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 207.0,
'tax_amount_sgst': 207.0,
'tax_amount_cess': 0.0,
}
]
}
yield 2, document, expected_values
# Change GST 5% taxes to IGST.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 12000.0, 'product_uom_id': self.uom_dozen, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': True,
'has_gst': True,
'has_cess': False,
'nb_columns': 8,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 7.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 1700.0,
'tax_amount_igst': 85.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_dozen.name,
'rate': 5.0,
'amount_untaxed': 12000.0,
'tax_amount_igst': 600.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 2300.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 207.0,
'tax_amount_sgst': 207.0,
'tax_amount_cess': 0.0,
},
],
}
yield 3, document, expected_values
# Put back the UOM of the second line to unit.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': True,
'has_gst': True,
'has_cess': False,
'nb_columns': 8,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 2300.0,
'tax_amount_igst': 115.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 2300.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 207.0,
'tax_amount_sgst': 207.0,
'tax_amount_cess': 0.0,
},
],
}
yield 4, document, expected_values
# Change GST 18% taxes to IGST.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': False,
'nb_columns': 6,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 2300.0,
'tax_amount_igst': 115.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 2300.0,
'tax_amount_igst': 414.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 5, document, expected_values
def test_l10n_in_hsn_summary_1_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_1():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_1_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_1():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_2(self):
""" Test CESS taxes in combination with GST/IGST. """
# Need the tax to be evaluated at the end.
self.cess_5_plus_1591.sequence = 100
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 15.80, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18 + self.cess_5_plus_1591},
]))
expected_values = {
'has_igst': False,
'has_gst': True,
'has_cess': True,
'nb_columns': 8,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 15.8,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 1.42,
'tax_amount_sgst': 1.42,
'tax_amount_cess': 2.38,
},
],
}
yield 1, document, expected_values
# Change GST 18% taxes to IGST.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 15.80, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18 + self.cess_5_plus_1591},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': True,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 15.8,
'tax_amount_igst': 2.84,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 2.38,
},
],
}
yield 2, document, expected_values
def test_l10n_in_hsn_summary_2_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_2():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_2_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_2():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_3(self):
""" Test with mixed HSN codes. """
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 50.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_2, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_2, 'quantity': 2.0, 'price_unit': 50.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': False,
'has_gst': True,
'has_cess': False,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 3.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 200.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 18.0,
'tax_amount_sgst': 18.0,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_2,
'quantity': 3.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 200.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 18.0,
'tax_amount_sgst': 18.0,
'tax_amount_cess': 0.0,
},
],
}
yield 1, document, expected_values
# Change GST 18% taxes to IGST.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 50.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_2, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_2, 'quantity': 2.0, 'price_unit': 50.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': False,
'nb_columns': 6,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 3.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 200.0,
'tax_amount_igst': 36.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_2,
'quantity': 3.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 200.0,
'tax_amount_igst': 36.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 2, document, expected_values
def test_l10n_in_hsn_summary_3_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_3():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_3_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_3():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_4(self):
""" Zero rated GST or no taxes at all."""
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit},
]))
expected_values = {
'has_igst': False,
'has_gst': False,
'has_cess': False,
'nb_columns': 5,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 2.0,
'uom_name': self.uom_unit.name,
'rate': 0.0,
'amount_untaxed': 700.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 1, document, expected_values
# No tax to Nil Rated/exempt.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.nil_rated},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.exempt_0},
]))
expected_values = {
'has_igst': False,
'has_gst': False,
'has_cess': False,
'nb_columns': 5,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 2.0,
'uom_name': self.uom_unit.name,
'rate': 0.0,
'amount_untaxed': 700.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 2, document, expected_values
# Put one IGST 18% to get a value on the IGST column.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.exempt_0},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': False,
'nb_columns': 6,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 350.0,
'tax_amount_igst': 63.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 0.0,
'amount_untaxed': 350.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 3, document, expected_values
def test_l10n_in_hsn_summary_4_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_4():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_4_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_4():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_5(self):
""" Test with discount. """
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 100.0, 'discount': 10.0, 'product_uom_id': self.uom_unit},
]))
expected_values = {
'has_igst': False,
'has_gst': False,
'has_cess': False,
'nb_columns': 5,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 0.0,
'amount_untaxed': 90.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 1, document, expected_values
def test_l10n_in_hsn_summary_5_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_5():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_5_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_5():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_6(self):
""" Test with Sale RC tax. """
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18_rc},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': False,
'nb_columns': 6,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 100.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 1, document, expected_values
def test_l10n_in_hsn_summary_6_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_6():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_6_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_6():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def test_l10n_in_hsn_summary_manual_edit_invoice_taxes(self):
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': '2017-01-01',
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'product_id': self.product_a.id,
'l10n_in_hsn_code': self.test_hsn_code_1,
'price_unit': 1000.0,
'tax_ids': [Command.set(self.gst_5.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'l10n_in_hsn_code': self.test_hsn_code_2,
'price_unit': 1000.0,
'tax_ids': [Command.set(self.gst_5.ids)],
}),
],
})
# Manual edition of the tax.
sgst_tax = self.gst_5.children_tax_ids.filtered(lambda tax: tax.l10n_in_gst_tax_type == 'sgst')
cgst_tax = self.gst_5.children_tax_ids.filtered(lambda tax: tax.l10n_in_gst_tax_type == 'cgst')
tax_line_sgst = invoice.line_ids.filtered(lambda aml: aml.tax_line_id == sgst_tax)
tax_line_cgst = invoice.line_ids.filtered(lambda aml: aml.tax_line_id == cgst_tax)
payment_term = invoice.line_ids.filtered(lambda aml: aml.display_type == 'payment_term')
invoice.line_ids = [
Command.update(tax_line_sgst.id, {'amount_currency': tax_line_sgst.amount_currency + 1.0}),
Command.update(tax_line_cgst.id, {'amount_currency': tax_line_cgst.amount_currency + 1.0}),
Command.update(payment_term.id, {'amount_currency': payment_term.amount_currency - 2.0}),
]
self.assert_invoice_l10n_in_hsn_summary(invoice, {
'has_igst': False,
'has_gst': True,
'has_cess': False,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 1000.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 24.5,
'tax_amount_sgst': 24.5,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_2,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 1000.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 24.5,
'tax_amount_sgst': 24.5,
'tax_amount_cess': 0.0,
},
],
})

View file

@ -0,0 +1,72 @@
from odoo import Command
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
from odoo.tests import tagged
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestInvoiceLabel(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_b.l10n_in_gst_treatment = "regular"
cls.unregistered_partner = cls.partner_a.copy({"state_id": cls.state_in_mh.id, "vat": None, "l10n_in_gst_treatment": "unregistered"})
def test_invoice_label(self):
# Regular with taxable items
regular_taxable_invoice = self._init_inv(
partner=self.partner_b,
taxes=self.igst_sale_18,
line_vals={'price_unit': 1000, 'quantity': 1},
)
invoice_label = regular_taxable_invoice._get_l10n_in_invoice_label()
self.assertEqual(invoice_label, 'Tax Invoice')
# Regular with exempt items
regular_exempt_invoice = self._init_inv(
partner=self.partner_b,
taxes=self.exempt,
line_vals={'price_unit': 1000, 'quantity': 1},
)
invoice_label = regular_exempt_invoice._get_l10n_in_invoice_label()
self.assertEqual(invoice_label, 'Bill of Supply')
# Regular with taxable and exempt items
regular_mix_invoice = self._init_inv(
partner=self.partner_b,
taxes=self.igst_sale_18,
line_vals={'price_unit': 1000, 'quantity': 1},
post=False,
)
regular_mix_invoice.write({
'invoice_line_ids': [Command.create({
'product_id': self.product_b.id,
'account_id': regular_mix_invoice.invoice_line_ids[0].account_id.id,
'price_unit': 500,
'quantity': 1,
'tax_ids': [(6, 0, [self.exempt.id])],
})],
})
regular_mix_invoice.action_post()
invoice_label = regular_mix_invoice._get_l10n_in_invoice_label()
self.assertEqual(invoice_label, 'Invoice')
# unregistered with taxable and exempt items
unregistered_invoice = self._init_inv(
partner=self.unregistered_partner,
taxes=self.igst_sale_18,
line_vals={'price_unit': 220000, 'quantity': 1},
post=False,
)
unregistered_invoice.write({
'invoice_line_ids': [Command.create({
'product_id': self.product_b.id,
'account_id': unregistered_invoice.invoice_line_ids[0].account_id.id,
'price_unit': 500,
'quantity': 1,
'tax_ids': [(6, 0, [self.exempt.id])],
})],
})
unregistered_invoice.action_post()
invoice_label = unregistered_invoice._get_l10n_in_invoice_label()
self.assertEqual(invoice_label, 'Invoice-cum-Bill of Supply')

View file

@ -0,0 +1,242 @@
from contextlib import contextmanager
from unittest.mock import patch
from odoo.tests import tagged
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
@tagged('post_install', '-at_install', 'post_install_l10n')
class TestFiscal(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Remove fiscal position 'fiscal_pos_a' so it does not show up on invoice
cls.partner_b.property_account_position_id = None
@contextmanager
def _set_env_company(self, companies):
cls = self.__class__
env = cls.env(context=dict(cls.env.context, allowed_company_ids=companies.ids))
with patch.object(cls, "env", env):
yield
def _assert_in_intra_state_fiscal_with_company(self, companies):
for company in companies:
state = company.state_id
name = state and 'Within %s' % state.name or 'Intra State'
self.assertRecordValues(
self.env['account.chart.template'].with_company(company).ref('fiscal_position_in_intra_state'),
[{
'name': name,
'state_ids': state.ids,
'company_id': company.id, # To make sure for branches don't get parent fiscal position
'auto_apply': True
}]
)
def _assert_invoice_fiscal_position(self, fiscal_position_ref, partner, taxes=None, move_type='out_invoice', post=True):
test_invoice = self.init_invoice(
move_type=move_type,
partner=partner,
post=post,
amounts=[110, 500],
taxes=taxes
)
self.assertEqual(test_invoice.fiscal_position_id, self.env['account.chart.template'].ref(fiscal_position_ref))
return test_invoice
def test_l10n_in_setting_up_company(self):
company = self._create_company(name='Fiscal Setup Test Company')
# Test with no state but country india
self._assert_in_intra_state_fiscal_with_company(company)
# Change state
company.write({'state_id': self.default_company.state_id.id})
self._assert_in_intra_state_fiscal_with_company(company)
# Change State Again
company.write({'state_id': self.env.ref('base.state_in_ap')})
self._assert_in_intra_state_fiscal_with_company(company)
def test_l10n_in_auto_apply_fiscal_invoices(self):
self._assert_in_intra_state_fiscal_with_company(self.default_company)
# Intra State
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_intra_state',
partner=self.partner_a,
taxes=self.sgst_sale_18,
)
# Inter State
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_inter_state',
partner=self.partner_b,
taxes=self.igst_sale_18,
)
# Outside India
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_export_sez_in',
partner=self.partner_foreign,
taxes=self.igst_sale_18,
)
def test_l10n_in_fiscal_for_branch(self):
branch_1 = self._create_company(
name='Branch 1',
parent_id=self.default_company.id,
state_id=self.partner_b.state_id.id, # Setting Partner B state will be now Intra State for branch 1
account_fiscal_country_id=self.country_in.id,
)
with self._set_env_company(self.outside_in_company):
branch_2 = self.env['res.company'].create({
'name': 'Branch 2',
'parent_id': self.default_company.id,
'account_fiscal_country_id': self.country_in.id,
'country_id': self.country_in.id,
})
# Check Branch with country india and no state
self._assert_in_intra_state_fiscal_with_company(branch_2)
# Set state after creating branch
branch_2.write({'state_id': self.env.ref('base.state_in_mp').id})
self._assert_in_intra_state_fiscal_with_company(branch_1 + branch_2)
# Invoice fiscal test with branch
with self._set_env_company(branch_1):
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_intra_state',
partner=self.partner_b,
taxes=self.sgst_sale_18,
)
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_inter_state',
partner=self.partner_a,
taxes=self.igst_sale_18,
)
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_export_sez_in',
partner=self.partner_foreign,
taxes=self.igst_sale_18,
)
def test_l10n_in_fiscal_in_bill_to_ship_to(self):
with self._set_env_company(self.default_company):
# Inter State
out_invoice = self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_inter_state',
partner=self.partner_b,
taxes=self.igst_sale_18,
post=False,
)
# Intra State
out_invoice.write({
'l10n_in_state_id': self.env.ref('base.state_in_gj').id,
})
self.assertEqual(
out_invoice.fiscal_position_id,
self.env['account.chart.template'].ref('fiscal_position_in_intra_state')
)
# Outside India (Export/SEZ)
out_invoice.write({
'l10n_in_state_id': self.env.ref('l10n_in.state_in_oc').id, # Other Country State
})
self.assertEqual(
out_invoice.fiscal_position_id,
self.env['account.chart.template'].ref('fiscal_position_in_export_sez_in')
)
def test_l10n_in_fiscal_in_vendor_bills(self):
'''
In Purchase Document: Compare place of supply with vendor state
'''
with self._set_env_company(self.default_company):
template = self.env['account.chart.template']
company_state = self.env.company.state_id
other_state = self.env['res.country.state'].search([
('id', '!=', company_state.id),
('country_id', '=', company_state.country_id.id)
], limit=1)
# Sub-test: Intra-State
with self.subTest(scenario="Intra-State"):
self.partner_a.write({'state_id': company_state.id})
vendor_bill = self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_intra_state',
partner=self.partner_a,
move_type='in_invoice',
post=False,
)
self.partner_a.write({'state_id': other_state.id})
vendor_bill.write({'l10n_in_state_id': other_state.id})
self.assertEqual(
vendor_bill.fiscal_position_id,
template.ref('fiscal_position_in_intra_state')
)
# Sub-test: Inter-State
with self.subTest(scenario="Inter-State"):
self.partner_a.write({'state_id': other_state.id})
vendor_bill = self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_inter_state',
partner=self.partner_a,
move_type='in_invoice',
post=False,
)
self.partner_a.write({'state_id': company_state.id})
vendor_bill.write({'l10n_in_state_id': other_state.id})
self.assertEqual(
vendor_bill.fiscal_position_id,
template.ref('fiscal_position_in_inter_state')
)
# Sub-test: Export/SEZ (Outside India)
with self.subTest(scenario="Export/SEZ"):
vendor_bill = self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_export_sez_in',
partner=self.partner_foreign,
move_type='in_invoice',
post=False,
)
vendor_bill.write({'l10n_in_state_id': self.env.ref('l10n_in.state_in_oc').id}) # Other Country State
self.assertEqual(
vendor_bill.fiscal_position_id,
template.ref('fiscal_position_in_export_sez_in')
)
# Here fpos should Inter-State. But due to `l10n_in_gst_treatment` it will be Export/SEZ
self.partner_a.write({'state_id': other_state.id})
vendor_bill.write({
'partner_id': self.partner_a.id, # Inter-State Partner
'l10n_in_state_id': company_state.id, # Company State
'l10n_in_gst_treatment': 'special_economic_zone',
})
self.assertEqual(
vendor_bill.fiscal_position_id,
template.ref('fiscal_position_in_sez')
)
# Sub-test: Manual Partner Fiscal Check
with self.subTest(scenario="Manual Partner Fiscal Check"):
# Here fpos should be Intra-State. But due to `property_account_position_id` it will be Export/SEZ
self.partner_a.write({
'state_id': company_state.id, # Intra-State Partner
'property_account_position_id': template.ref('fiscal_position_in_export_sez_in').id
})
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_export_sez_in',
partner=self.partner_a,
move_type='in_invoice',
)
def test_l10n_in_company_with_no_vat(self):
"""
Test the company with no VAT and update the partner and company states as per the GSTIN number
"""
company = self.default_company
company.write({'vat': False})
self.assertFalse(company.vat)
company.action_update_state_as_per_gstin()
self.assertEqual(company.partner_id.state_id, self.env.ref('base.state_in_gj'))
company.write({'vat': '36AABCT1332L011'})
company.action_update_state_as_per_gstin()
self.assertEqual(company.state_id, self.env.ref('base.state_in_ts'))

View file

@ -0,0 +1,135 @@
from odoo.tests import tagged
import logging
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
_logger = logging.getLogger(__name__)
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestReports(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_b.l10n_in_gst_treatment = 'regular'
cls.partner_a.l10n_in_gst_treatment = 'composition'
cls.partner_foreign.l10n_in_gst_treatment = 'overseas'
cls.partner_foreign_no_state.l10n_in_gst_treatment = 'overseas'
cls.igst_sale_18 = cls.env['account.chart.template'].ref('igst_sale_18')
def test_partner_details_change_with_invoice(self):
invoice_b_2 = self.init_invoice(
move_type='out_invoice',
partner=self.partner_b,
amounts=[250, 600],
taxes=[self.igst_sale_18],
post=True
)
# Place of Supply (pos) is same as of state of partner (for journal type sale)
expected_pos_id = self.state_in_mh.id
self.assertRecordValues(
self.invoice_b,
[{
'state': 'draft',
'l10n_in_gst_treatment': self.partner_b.l10n_in_gst_treatment,
'l10n_in_state_id': expected_pos_id,
}]
)
self.assertRecordValues(
invoice_b_2,
[{
'state': 'posted',
'l10n_in_gst_treatment': self.partner_b.l10n_in_gst_treatment,
'l10n_in_state_id': expected_pos_id,
}]
)
self.assertRecordValues(
invoice_b_2,
[{ # check gst treatment and pos doesn't change on posted invoice
'state': 'posted',
'l10n_in_gst_treatment': 'regular',
}]
)
def test_partner_change_with_invoice(self):
in_invoice = self.init_invoice(
move_type='in_invoice',
partner=self.partner_b,
amounts=[452, 58, 110],
taxes=[self.igst_sale_18],
)
self.invoice_a.partner_id = self.partner_foreign
self.assertRecordValues(
self.invoice_a,
[{
'state': 'draft',
'l10n_in_gst_treatment': self.partner_foreign.l10n_in_gst_treatment,
'l10n_in_state_id': self.env.ref("l10n_in.state_in_oc").id,
}]
)
self.assertRecordValues(
in_invoice,
[{
'state': 'draft',
'l10n_in_gst_treatment': self.partner_b.l10n_in_gst_treatment,
'l10n_in_state_id': self.env.company.state_id.id,
}]
)
def test_place_of_supply(self):
child_partner = self.env['res.partner'].create({
'name': "Child Contact",
'type': "delivery",
'parent_id': self.partner_a.id,
'state_id': self.state_in_hp.id
})
self.assertRecordValues(
self.invoice_a,
[{
'partner_shipping_id': self.partner_a.id,
'l10n_in_state_id': self.partner_a.state_id.id,
}]
)
self.invoice_a.partner_shipping_id = child_partner
self.assertRecordValues(
self.invoice_a,
[{
'partner_shipping_id': child_partner.id,
'l10n_in_state_id': child_partner.state_id.id,
}]
)
self.invoice_a.partner_shipping_id = self.partner_b
self.assertRecordValues(
self.invoice_a,
[{
'l10n_in_state_id': self.partner_a.state_id.id,
}]
)
def test_foreign_customer_without_state(self):
""" Verify foreign customer without state_id gets foreign state reference """
self.assertRecordValues(
self.invoice_d,
[{
'l10n_in_gst_treatment': 'overseas',
'l10n_in_state_id': self.env.ref("l10n_in.state_in_oc").id,
}]
)
def test_government_gstin_extraction_tan(self):
""" Verify that a GSTIN based on a TAN (Government entity) correctly populates the TAN field and leaves the PAN field empty. """
gov_partner = self.env['res.partner'].create({
'name': "Gov Partner",
'country_id': self.env.ref('base.in').id,
'state_id': self.env.ref('base.state_in_dl').id,
'vat': '07DELN10357E1DH',
})
self.assertRecordValues(gov_partner, [{
'l10n_in_pan_entity_id': False,
'l10n_in_tan': 'DELN10357E',
}])

View file

@ -0,0 +1,596 @@
from odoo import Command
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
from odoo.tests import tagged
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestTdsTcsAlert(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
ChartTemplate = cls.env['account.chart.template']
# ==== Chart of Accounts ====
cls.purchase_account = ChartTemplate.ref('p2107')
cls.purchase_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tds_section_194c').id
})
cls.house_expense_account = ChartTemplate.ref('p2103')
cls.house_expense_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tds_section_194c').id
})
cls.internet_account = ChartTemplate.ref('p2105')
cls.internet_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tds_section_194j').id
})
cls.rent_account = ChartTemplate.ref('p2111')
cls.rent_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tds_section_194ib').id
})
cls.sale_account = ChartTemplate.ref('p20011')
cls.sale_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tcs_section_206c1g_r').id
})
cls.service_account = ChartTemplate.ref('p20021')
cls.creditors_account = ChartTemplate.ref('p11211')
# ==== Taxes ====
cls.tax_194c = ChartTemplate.ref('tds_20_us_194c')
cls.tax_194c.write({'l10n_in_section_id': cls.env.ref('l10n_in.tds_section_194c').id})
cls.tax_194j = ChartTemplate.ref('tds_10_us_194j')
cls.tax_194j.write({'l10n_in_section_id': cls.env.ref('l10n_in.tds_section_194j').id})
cls.tax_194ib = ChartTemplate.ref('tds_20_us_194ib')
cls.tax_194ib.write({'l10n_in_section_id': cls.env.ref('l10n_in.tds_section_194ib').id})
cls.tax_206c1g_r = ChartTemplate.ref('tcs_5_us_206c_1g_som')
cls.tax_206c1g_r.write({'l10n_in_section_id': cls.env.ref('l10n_in.tcs_section_206c1g_r').id})
country_in_id = cls.env.ref("base.in").id
cls.partner_b.write({
'vat': '27ABCPM8965E1ZE',
})
cls.partner_foreign_2 = cls.partner_foreign.copy()
# ==== Company ====
cls.env.company.write({
'child_ids': [
Command.create({
'name': 'Branch A',
"state_id": cls.env.ref("base.state_in_gj").id,
'account_fiscal_country_id': country_in_id,
'country_id': country_in_id,
}),
Command.create({
'name': 'Branch B',
"state_id": cls.env.ref("base.state_in_mh").id,
'account_fiscal_country_id': country_in_id,
'country_id': country_in_id,
}),
Command.create({
'name': 'Branch C',
"state_id": cls.env.ref("base.state_in_mp").id,
'account_fiscal_country_id': country_in_id,
'country_id': country_in_id,
}),
],
})
cls.cr.precommit.run() # load the CoA
cls.branch_a, cls.branch_b, cls.branch_c = cls.env.company.child_ids
def create_invoice(self, move_type=None, partner=None, invoice_date=None, amounts=None, taxes=[], company=None, accounts=[], quantities=[]):
invoice = self.init_invoice(
move_type=move_type or 'in_invoice',
partner=partner,
invoice_date=invoice_date,
post=False,
amounts=amounts,
company=company
)
for i, account in enumerate(accounts):
invoice.invoice_line_ids[i].account_id = account
for i, quantity in enumerate(quantities):
invoice.invoice_line_ids[i].quantity = quantity
for i, tax in enumerate(taxes):
invoice.invoice_line_ids[i].tax_ids = tax
invoice.action_post()
return invoice
def tds_wizard_entry(self, move, lines):
journal_id = self.env['account.journal'].search([('company_id', '=', self.env.company.id),('type', '=', 'general')], limit=1)
for tax, amount in lines:
self.env['l10n_in.withhold.wizard'].with_context(active_model='account.move', active_ids=move.ids).create({
'journal_id': journal_id.id,
'tax_id': tax.id,
'base': amount,
'date': move.invoice_date,
}).action_create_and_post_withhold()
def reverse_move(self, move, date):
move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=move.ids).create({
'date': date,
'reason': 'no reason',
'journal_id': move.journal_id.id,
})
return move_reversal.refund_moves()
def test_tcs_tds_warning(self):
'''
Test that if any of the limit is not exceeded.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[29000],
company=self.branch_a,
accounts=[self.internet_account],
quantities=[1]
)
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_on_exceeded_per_transaction_limit(self):
'''
Test that if the per transaction limit is exceeded.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[31000],
company=self.branch_a,
quantities=[1]
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
move_1 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-06-05',
amounts=[31000],
company=self.branch_b,
quantities=[1]
)
self.assertEqual(move_1.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
self.create_invoice(
partner=self.partner_b,
invoice_date='2024-06-05',
amounts=[31000],
company=self.branch_b,
quantities=[1]
)
move_3 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-06-05',
amounts=[31000],
company=self.branch_b,
quantities=[1]
)
self.assertEqual(move_3.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_on_monthly_aggregate_limit(self):
'''
Test the monthly aggregate limit, the warning
message should be set accordingly.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_a,
accounts=[self.rent_account]
)
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-07-06',
amounts=[20000],
company=self.branch_b,
accounts=[self.rent_account]
)
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-07-16',
amounts=[31000],
company=self.branch_c,
accounts=[self.rent_account]
)
self.assertEqual(move_2.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194IB on this transaction.")
move_3 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-09-06',
amounts=[50000],
company=self.branch_c,
accounts=[self.rent_account]
)
self.assertEqual(move_3.l10n_in_warning, False)
move_4 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-09-16',
amounts=[50000],
company=self.branch_c,
accounts=[self.rent_account]
)
self.assertEqual(move_4.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194IB on this transaction.")
def test_tcs_tds_warning_partner_wiht_pan(self):
'''
Test the aggregate limit when partner don't have
pan number and having pan number.
'''
# no pan number
move = self.create_invoice(
partner=self.partner_foreign,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_foreign_2,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_b,
accounts=[self.internet_account]
)
self.assertEqual(move_1.l10n_in_warning, False)
# same pan number
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.assertEqual(move_2.l10n_in_warning, False)
move_3 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_b,
accounts=[self.internet_account]
)
self.assertEqual(move_3.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194J on this transaction.")
def test_tcs_tds_warning_on_exceeded_aggregate_limit(self):
'''
Test that if the aggregate limit is exceeded.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[20000],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-07-06',
amounts=[20000],
company=self.branch_b,
)
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-08-06',
amounts=[31000],
company=self.branch_c,
)
self.assertEqual(move_2.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
move_3 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-09-06',
amounts=[5000],
company=self.branch_a,
)
self.assertEqual(move_3.l10n_in_warning, False)
move_4 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-10-07',
amounts=[20000],
company=self.branch_b,
)
self.assertEqual(move_4.l10n_in_warning, False)
move_5 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-11-08',
amounts=[25000],
company=self.branch_c,
)
self.assertEqual(move_5.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_on_case_of_credit_note(self):
'''
Test that the aggregate limit in case of debit/credit note.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-09-01',
amounts=[2000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-09-01',
amounts=[3000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.reverse_move(move, '2024-09-01')
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-09-01',
amounts=[2000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.assertEqual(move_2.l10n_in_warning, False)
def test_tcs_tds_warning_cleared_on_available_tax(self):
'''
Test when a tax is added to the move line with a similar tax group
as the account.
'''
move = self.create_invoice(
partner=self.partner_a,
move_type='out_invoice',
invoice_date='2022-12-12',
amounts=[710000],
taxes=[self.tax_206c1g_r],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_for_multiple_accounts_in_lines(self):
'''
Test when there are multiple products in the move line and some of them
have different accounts which have the different tax group as the account.
'''
move = self.create_invoice(
partner=self.partner_a,
move_type='in_invoice',
invoice_date='2022-12-12',
amounts=[100000, 1100000, 710000],
company=self.branch_a,
accounts=[self.rent_account, self.internet_account, self.purchase_account],
quantities=[15, 16, 10]
)
self.assertTrue(move.l10n_in_warning)
move_1 = self.create_invoice(
partner=self.partner_a,
move_type='in_invoice',
invoice_date='2022-12-12',
amounts=[1000000.0, 1100000.0, 710000],
company=self.branch_a,
accounts=[self.rent_account, self.internet_account, self.purchase_account],
)
self.tds_wizard_entry(move=move_1, lines=[(self.tax_194ib, 100000), (self.tax_194j, 100000), (self.tax_194c, 100000)])
move_1.button_draft()
move_1.action_post()
self.assertEqual(move_1.l10n_in_warning, False)
def test_tcs_tds_warning_for_if_line_has_price_zero(self):
'''
Test when any invoice line has Zero
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[101000, 0],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
move_1 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[0],
company=self.branch_a,
)
self.assertEqual(move_1.l10n_in_warning, False)
def test_tcs_tds_warning_for_all_lines_do_not_have_taxes(self):
'''
Test when tds entry created and warning will removed
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[1000, 6000],
company=self.branch_a,
accounts=[],
quantities=[15, 16]
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
self.tds_wizard_entry(move=move, lines=[(self.tax_194c, 100000)])
move.line_ids.remove_move_reconcile()
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_for_company_branches(self):
'''
Test when the aggregate limit is exceeded in case of multiple branches
of the company,the warning message should be set accordingly.
'''
self.create_invoice(
partner=self.partner_a,
invoice_date='2024-05-14',
amounts=[25000],
company=self.branch_a,
)
self.create_invoice(
partner=self.partner_b,
invoice_date='2024-05-14',
amounts=[25000],
company=self.branch_b,
)
self.create_invoice(
partner=self.partner_b,
invoice_date='2024-05-14',
amounts=[25000],
company=self.branch_c,
quantities=[25]
)
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-05-14',
amounts=[28000],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_tcs_use_in_bill(self):
'''
Test when tcs section is used in the bill creation.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-05-29',
amounts=[1100000],
company=self.branch_a,
accounts=[self.sale_account]
)
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_tds_use_in_invoice(self):
'''
Test when tcs section is used in the bill creation.
'''
move = self.create_invoice(
move_type='out_invoice',
partner=self.partner_a,
invoice_date='2024-05-29',
amounts=[110000],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_for_multiple_accounts_same_section_in_lines(self):
'''
Test when there are multiple products in the move line and some of them
have different accounts which have the same tax group as the account.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[17000, 14000],
company=self.branch_a,
accounts=[self.house_expense_account, self.purchase_account],
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
move_1 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[17000, 13000],
company=self.branch_a,
accounts=[self.house_expense_account, self.purchase_account],
)
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[30000],
company=self.branch_a,
accounts=[self.house_expense_account],
)
self.assertEqual(move_2.l10n_in_warning, False)
move_3 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[10000],
company=self.branch_a,
)
self.assertEqual(move_3.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_for_not_consider_draft_cancel_invoices_for_aggregate(self):
'''
Test to exclude draft and canceled invoices from aggregate
total calculation.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[16000],
company=self.branch_a,
accounts=[self.purchase_account],
)
move.button_cancel()
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[25000],
company=self.branch_a,
accounts=[self.purchase_account],
)
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[85000],
company=self.branch_a,
accounts=[self.purchase_account],
)
self.assertEqual(move_2.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_if_some_lines_has_tax(self):
'''
Test when a tax is added to the some of the move line
'''
move = self.create_invoice(
partner=self.partner_a,
move_type='out_invoice',
invoice_date='2022-12-12',
amounts=[710000, 710000],
taxes=[self.tax_206c1g_r],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to collect TCS u/s 206C(1G) Remittance on this transaction.")

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_account_tds_tcs_view_form_inherit" model="ir.ui.view">
<field name="name">account.account.tds.tcs.view.form.inherit</field>
<field name="model">account.account</field>
<field name="inherit_id" ref="account.view_account_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='tax_ids']" position="after">
<field name="l10n_in_tds_tcs_section_id" invisible="company_fiscal_country_code != 'IN' or (not l10n_in_tds_feature_enabled and not l10n_in_tcs_feature_enabled)"/>
</xpath>
</field>
</record>
<record id="account_account_tds_tcs_view_tree_inherit" model="ir.ui.view">
<field name="name">account.account.tds.tcs.view.list.inherit</field>
<field name="model">account.account</field>
<field name="inherit_id" ref="account.view_account_list"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='account_type']" position="after">
<field name="l10n_in_tds_tcs_section_id" optional="hide"/>
</xpath>
</field>
</record>
</odoo>

View file

@ -5,32 +5,116 @@
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="before">
<div class="alert alert-warning mt-1 mb-1" role="alert" invisible="not l10n_in_warning">
<div>
<field name="l10n_in_warning" widget="actionable_errors"/>
</div>
</div>
</xpath>
<xpath expr="//field[@name='ref']" position="after">
<field name="country_code" invisible="1"/>
<field name="l10n_in_journal_type" invisible="1"/>
<field name="l10n_in_state_id" domain="[('country_id.code', '=', 'IN')]" options="{'no_create': True, 'no_open': True}"
attrs="{'invisible': ['|', ('country_code', '!=', 'IN'), ('move_type', '=', 'entry')], 'required': [('country_code', '=', 'IN'), ('move_type', '!=', 'entry'), ('l10n_in_journal_type', 'in', ('sale', 'purchase'))], 'readonly': [('state', '!=', 'draft')]}"/>
<field name="l10n_in_state_id" domain="[('country_id.code', '=', 'IN')]"
options="{'no_create': True, 'no_open': True}"
invisible="country_code != 'IN' or not l10n_in_is_gst_registered_enabled or move_type == 'entry'"
readonly="state != 'draft'"
required="country_code == 'IN' and l10n_in_is_gst_registered_enabled and state == 'draft' and move_type != 'entry' and l10n_in_journal_type in ('sale', 'purchase')"/>
<field name="l10n_in_gst_treatment"
attrs="{'invisible': ['|', ('country_code', '!=', 'IN'), ('move_type', '=', 'entry')], 'required': [('country_code', '=', 'IN'), ('move_type', '!=', 'entry')], 'readonly': [('state', '!=', 'draft')]}"/>
invisible="country_code != 'IN' or not l10n_in_is_gst_registered_enabled or move_type == 'entry'"
readonly="state != 'draft'"
required="country_code == 'IN' and l10n_in_is_gst_registered_enabled and state == 'draft' and move_type != 'entry'"/>
<label for="l10n_in_partner_gstin_status"
invisible="not l10n_in_show_gstin_status"/>
<div name="status_date_container"
invisible="not l10n_in_show_gstin_status">
<field name="l10n_in_partner_gstin_status" class="d-none"/>
<span class="text-nowrap" readonly="1">
<span invisible="not l10n_in_partner_gstin_status"
class="oe_inline text-success">Active</span>
<span invisible="not l10n_in_gstin_verified_date or l10n_in_partner_gstin_status"
class="oe_inline text-danger">Inactive</span>
<span class="text-muted oe_inline">
<span invisible="l10n_in_gstin_verified_date">Not Checked</span>
<span invisible="not l10n_in_gstin_verified_date" class="ps-3">Checked: </span>
<field name="l10n_in_gstin_verified_date" class="oe_inline" widget="remaining_days"/>
<button name="l10n_in_verify_partner_gstin_status"
type="object" icon="fa-refresh"
class="oe_link p-0 ps-3" title="Verify GSTIN status" />
</span>
</span>
</div>
<field name="reversed_entry_id" domain="[('move_type', '=', 'out_invoice'), ('commercial_partner_id', '=', commercial_partner_id)]" readonly="state != 'draft'" invisible="move_type != 'out_refund' or country_code != 'IN'"/>
</xpath>
<xpath expr="//page[@id='other_tab']/group[@id='other_tab_group']" position="after">
<group string="Export India" attrs="{'invisible': ['|', ('l10n_in_gst_treatment', 'not in', ['overseas', 'deemed_export']), ('move_type', 'not in', ['out_invoice', 'out_refund'])]}">
<field name="l10n_in_shipping_bill_number"/>
<field name="l10n_in_shipping_bill_date"/>
<field name="l10n_in_shipping_port_code_id"/>
<group string="Export India" invisible="l10n_in_gst_treatment not in ['overseas', 'deemed_export'] or move_type not in ['out_invoice', 'out_refund']">
<field name="l10n_in_shipping_bill_number" readonly="state != 'draft'"/>
<field name="l10n_in_shipping_bill_date" readonly="state != 'draft'"/>
<field name="l10n_in_shipping_port_code_id" readonly="state != 'draft'"/>
</group>
<group string="Import India" attrs="{'invisible': ['|', ('l10n_in_gst_treatment', 'not in', ['overseas', 'special_economic_zone']), ('move_type', 'not in', ['in_invoice', 'in_refund'])]}">
<field name="l10n_in_shipping_bill_number" string="Bill of Entry Number"/>
<field name="l10n_in_shipping_bill_date" string="Bill of Entry Date"/>
<field name="l10n_in_shipping_port_code_id"/>
<group string="Import India" invisible="l10n_in_gst_treatment not in ['overseas', 'special_economic_zone'] or move_type not in ['in_invoice', 'in_refund']">
<field name="l10n_in_shipping_bill_number" string="Bill of Entry Number" readonly="state != 'draft'"/>
<field name="l10n_in_shipping_bill_date" string="Bill of Entry Date" readonly="state != 'draft'"/>
<field name="l10n_in_shipping_port_code_id" readonly="state != 'draft'"/>
</group>
</xpath>
<xpath expr="//field[@name='partner_id']" position="after">
<xpath expr="//field[@name='partner_shipping_id']" position="before">
<field name="l10n_in_reseller_partner_id"
groups="l10n_in.group_l10n_in_reseller"
attrs="{'invisible': ['|', '|',('move_type', 'not in', ('out_invoice', 'out_refund')), ('country_code', '!=', 'IN'), ('move_type', '=', 'entry')]}"
/>
invisible="move_type not in ('out_invoice', 'out_refund') or country_code != 'IN' or move_type == 'entry'"
readonly="state != 'draft'"/>
</xpath>
<xpath expr="//field[@name='invoice_line_ids']/list//field[@name='product_id']" position="after">
<field name="l10n_in_hsn_code" optional="hide" column_invisible="parent.country_code != 'IN'"/>
</xpath>
<xpath expr="//field[@name='journal_line_ids' or @name='line_ids']/list//field[@name='name']" position="after">
<field name="l10n_in_hsn_code" optional="hide" column_invisible="parent.country_code != 'IN'"/>
</xpath>
<xpath expr="//header" position="inside">
<button name="%(l10n_in_withholding_entry_form_action)d" string="TDS Entry" type="action" class="btn btn-secondary float-end"
invisible="country_code != 'IN' or l10n_in_tds_deduction == 'no' or not l10n_in_tds_feature_enabled or move_type not in ('out_invoice', 'in_invoice', 'out_refund', 'in_refund') or state != 'posted'"/>
<button name="action_l10n_in_apply_higher_tax" string="Apply Higher TCS" type="object" class="btn btn-secondary float-end"
invisible="not l10n_in_display_higher_tcs_button or not l10n_in_tcs_feature_enabled"/>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_l10n_in_withholding_entries"
class="oe_stat_button"
type="object"
icon="fa-list-alt"
invisible="not l10n_in_tds_feature_enabled or not l10n_in_withhold_move_ids">
<div class="o_field_widget o_stat_info">
<span class="o_stat_text">TDS</span>
<span class="o_stat_value"><field name="l10n_in_total_withholding_amount"/></span>
</div>
</button>
</xpath>
<xpath expr="//notebook/page[@id='aml_tab']" position="before">
<page name="withholding_tab" string="TDS Information" invisible="not l10n_in_tds_feature_enabled or not l10n_in_withholding_line_ids">
<field name="l10n_in_withholding_line_ids" nolabel="1" colspan="4">
<list editable="bottom" string="TDS Information">
<field name="tax_ids" string="Tax" widget="many2many_tax_tags"/>
<field name="price_subtotal" string="Base Amount" sum="Total"/>
<field name="l10n_in_withhold_tax_amount" string="TDS Amount" sum="Total"/>
</list>
</field>
</page>
</xpath>
</field>
</record>
<record id="view_move_line_list_l10n_in_withholding" model="ir.ui.view">
<field name="name">account.move.line.list.l10n.in.withholding</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<list string="Journal items" editable="top" create="0" default_group_by="l10n_in_tds_tcs_section_id">
<field name="product_id" readonly="1"/>
<field name="name" widget="section_and_note_text"/>
<field name="account_id"/>
<field name="tax_ids"
widget="many2many_tax_tags"
domain="[('type_tax_use', '=', context.get('default_tax_type_use'))]"
column_invisible="context.get('move_type')"/>
<field name="l10n_in_tds_tcs_section_id" string="Suggested Section"/>
<field name="price_total"/>
</list>
</field>
</record>
</odoo>

View file

@ -5,14 +5,11 @@
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
<field name='profit_account_id' position="attributes">
<attribute name="attrs">{'invisible': ['|', '&amp;', ('country_code', '!=', 'IN'), ('type', '!=', 'cash'), '&amp;', ('country_code', '=', 'IN'), ('type', 'not in', ['bank', 'cash', 'sale', 'purchase'])]}</attribute>
<field name="profit_account_id" position="attributes">
<attribute name="invisible">country_code != 'IN' and type != 'cash' or country_code == 'IN' and type not in ['bank', 'cash', 'sale', 'purchase']</attribute>
</field>
<field name='loss_account_id' position="attributes">
<attribute name="attrs">{'invisible': ['|', '&amp;', ('country_code', '!=', 'IN'), ('type', '!=', 'cash'), '&amp;', ('country_code', '=', 'IN'), ('type', 'not in', ['bank', 'cash', 'sale', 'purchase'])]}</attribute>
</field>
<field name="company_id" position="after">
<field name="l10n_in_gstin_partner_id" context="{'show_vat':True}" options='{"no_create": True,"always_reload": True}' attrs="{'invisible': [('country_code', '!=', 'IN')]}"/>
<field name="loss_account_id" position="attributes">
<attribute name="invisible">country_code != 'IN' and type != 'cash' or country_code == 'IN' and type not in ['bank', 'cash', 'sale', 'purchase']</attribute>
</field>
</field>
</record>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_move_line_tree_hsn_l10n_in" model="ir.ui.view">
<field name="name">account.move.line.list.l10n_in</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<list string="Products" editable="top" create="0">
<field name="move_id"
optional="show"
column_invisible="not context.get('send_and_print')"/>
<field name="product_id" readonly="1"/>
<field name="name" widget="section_and_note_text"/>
<field name="l10n_in_hsn_code"
optional="show"
column_invisible="context.get('tax_validation') or context.get('restrict_negative_discount_line')"/>
<field name="tax_ids"
optional="show"
widget="many2many_tax_tags" domain="[('type_tax_use', '=', 'sale')]"
column_invisible="not context.get('tax_validation', False)"/>
<field name="discount"
optional="show"
column_invisible="not context.get('restrict_negative_discount_line', False)"/>
</list>
</field>
</record>
<record id="view_move_line_tree_l10n_in" model="ir.ui.view">
<field name="name">account.move.line.list.l10n_in</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='credit']" position="after">
<field name="l10n_in_gstr_section" optional="hide" readonly="1"/>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_payment_form_inherit_l10n_in_withholding" model="ir.ui.view">
<field name="name">account.payment.form.inherit.l10n_in_withholding</field>
<field name="model">account.payment</field>
<field name="inherit_id" ref="account.view_account_payment_form"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<button name="%(l10n_in_withholding_entry_form_action)d" string="TDS Entry" type="action" class="btn btn-secondary float-end"
invisible="country_code != 'IN' or not l10n_in_tds_feature_enabled or state not in ('in_process', 'paid') or is_reconciled"/>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_l10n_in_withholding_entries"
class="oe_stat_button"
type="object"
icon="fa-list-alt"
invisible="not l10n_in_tds_feature_enabled or not l10n_in_withhold_move_ids">
<div class="o_field_widget o_stat_info">
<span class="o_stat_text">TDS</span>
<span class="o_stat_value"><field name="l10n_in_total_withholding_amount"/></span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View file

@ -5,9 +5,16 @@
<field name="model">account.tax</field>
<field name="inherit_id" ref="account.view_tax_form"/>
<field name="arch" type="xml">
<field name="is_base_affected" position="after">
<field name="l10n_in_reverse_charge" attrs="{'invisible':['|', ('amount_type','=', 'group'), ('country_code', '!=', 'IN')]}"/>
<field name="tax_scope" position="after">
<field name="l10n_in_tax_type" invisible="amount_type == 'group' or country_code != 'IN'"/>
</field>
<field name="is_base_affected" position="after">
<field name="l10n_in_reverse_charge" invisible="amount_type == 'group' or country_code != 'IN' or l10n_in_tax_type != 'gst'"/>
<field name="l10n_in_is_lut" invisible="amount_type == 'group' or country_code != 'IN' or l10n_in_tax_type != 'gst'"/>
</field>
<xpath expr="//field[@name='tax_group_id']" position="after">
<field name="l10n_in_section_id" invisible="country_code != 'IN' or not (l10n_in_tds_feature_enabled and l10n_in_tcs_feature_enabled) or l10n_in_tax_type not in ['tcs', 'tds_sale', 'tds_purchase']"/>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="l10n_in_pan_entity_view_form" model="ir.ui.view">
<field name="name">l10n_in.pan.entity.view.form</field>
<field name="model">l10n_in.pan.entity</field>
<field name="arch" type="xml">
<form string="PAN Entity">
<div class="alert alert-warning w-100 d-flex align-items-center gap-1" invisible="tds_deduction != 'lower' or message_attachment_count" role="alert">
<span>Attaching a certificate would provide better clarity and understanding.</span>
</div>
<sheet>
<group>
<group>
<field name="name" help="Permanent Account Number"/>
<field name="type"/>
<field name="partner_ids" widget="many2many_tags_avatar" options="{'no_create': True}"/>
<field name="tds_deduction" required="1"/>
<field name="tds_certificate"
widget="binary"
filename="tds_certificate_filename"
options="{'accepted_file_extensions': '.pdf', 'allowed_mime_type' : 'application/pdf'}"
/>
<field name="tds_certificate_filename" invisible="1"/> <!-- Hidden field to store the filename of the uploaded certificate -->
</group>
<group>
<field name="msme_type"/>
<field name="msme_number" placeholder="e.g. UDYAM-XX-00-0000000"/>
</group>
</group>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="l10n_in_pan_entity_view_tree" model="ir.ui.view">
<field name="name">l10n_in.pan.entity.view.tree</field>
<field name="model">l10n_in.pan.entity</field>
<field name="arch" type="xml">
<list string="PAN Entity">
<field name="name"/>
<field name="type"/>
<field name="partner_ids" widget="many2many_tags_avatar" options="{'no_create': True}"/>
</list>
</field>
</record>
<record id="l10n_in_pan_entity_action" model="ir.actions.act_window">
<field name="name">PAN Entity</field>
<field name="res_model">l10n_in.pan.entity</field>
<field name="view_mode">list,form</field>
</record>
<menuitem
id="menu_l10n_in_pan_entity"
name="PAN Entity"
parent="account.account_transactions_menu"
action="l10n_in_pan_entity_action"
/>
</odoo>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="l10n_in_section_alert_view_tree" model="ir.ui.view">
<field name="name">l10n_in.section.alert.view.list</field>
<field name="model">l10n_in.section.alert</field>
<field name="arch" type="xml">
<list string="Section">
<field name="name"/>
<field name="tax_source_type"/>
<field name="consider_amount"/>
<field name="per_transaction_limit"/>
<field name="aggregate_limit"/>
</list>
</field>
</record>
<record id="l10n_in_section_alert_view_form" model="ir.ui.view">
<field name="name">l10n_in.section.alert.view.form</field>
<field name="model">l10n_in.section.alert</field>
<field name="arch" type="xml">
<form string="Section">
<sheet>
<div class="oe_title">
<label for="name" string="Section Name"/>
<h1>
<field name="name"/>
</h1>
</div>
<group class="w-50" string="Threshold limits">
<field name="consider_amount"/>
<label for="is_per_transaction_limit"/>
<div>
<field class="w-25" name="is_per_transaction_limit" widget="boolean_toggle"/>
<field class="w-25 text-center oe_inline" name="per_transaction_limit" invisible="not is_per_transaction_limit"/>
</div>
<label for="is_aggregate_limit"/>
<div>
<field class="w-25" name="is_aggregate_limit" widget="boolean_toggle"/>
<field class="w-25 text-center oe_inline" name="aggregate_limit" invisible="not is_aggregate_limit"/>
<field class="w-25" name="aggregate_period" invisible="not is_aggregate_limit"/>
</div>
</group>
</sheet>
</form>
</field>
</record>
<record id="l10n_in_section_alert_action" model="ir.actions.act_window">
<field name="name">Section</field>
<field name="res_model">l10n_in.section.alert</field>
<field name="view_mode">list,form</field>
</record>
</odoo>

View file

@ -20,14 +20,14 @@
</record>
<record id="l10n_in_port_code_tree_view" model="ir.ui.view">
<field name="name">l10n_in.port.code.tree</field>
<field name="name">l10n_in.port.code.list</field>
<field name="model">l10n_in.port.code</field>
<field name="arch" type="xml">
<tree string="India Port Code">
<list string="India Port Code">
<field name="name"/>
<field name="code"/>
<field name="state_id"/>
</tree>
</list>
</field>
</record>
@ -38,7 +38,7 @@
<search string="India Port Code">
<field name="name" string="Port" filter_domain="['|',('name', 'ilike', self),('code', 'ilike', self)]"/>
<field name="state_id"/>
<group expand="0" string="Group By">
<group>
<filter string="State" name="state" domain="[]" context="{'group_by': 'state_id'}"/>
</group>
</search>

View file

@ -6,9 +6,13 @@
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="before">
<div class="alert alert-warning" role="alert" invisible="'IN' not in fiscal_country_codes or not l10n_in_hsn_warning">
<field name="l10n_in_hsn_warning"/>
</div>
</xpath>
<field name="categ_id" position="after">
<field name="l10n_in_hsn_code"/>
<field name="l10n_in_hsn_description"/>
<field name="l10n_in_hsn_code" widget="l10n_in_hsn_autocomplete" invisible="'IN' not in fiscal_country_codes or not l10n_in_is_gst_registered_enabled"/>
</field>
</field>
</record>

View file

@ -2,10 +2,19 @@
<odoo>
<template id="l10n_in_report_invoice_document_inherit" inherit_id="account.report_invoice_document" primary="True">
<xpath expr="//div[@name='shipping_address_block']" position="inside">
<div t-if="o.company_id.account_fiscal_country_id.code == 'IN' and o.partner_shipping_id.vat">
<div t-if="o.company_id.account_fiscal_country_id.code == 'IN' and o.company_id.l10n_in_is_gst_registered and o.partner_shipping_id.vat">
GSTIN: <span t-field="o.partner_shipping_id.vat"/>
</div>
</xpath>
<xpath expr="//div[@id='partner_vat_address_same_as_shipping']" position="attributes">
<attribute name="t-if" add="o.company_id.l10n_in_is_gst_registered" separator="and"/>
</xpath>
<xpath expr="//div[@id='partner_vat_address_not_same_as_shipping']" position="attributes">
<attribute name="t-if" add="o.company_id.l10n_in_is_gst_registered" separator="and"/>
</xpath>
<xpath expr="//div[@id='partner_vat_no_shipping']" position="attributes">
<attribute name="t-if" add="o.company_id.l10n_in_is_gst_registered" separator="and"/>
</xpath>
<xpath expr="//div[@name='address_not_same_as_shipping']//t[@t-set='address']" position="inside">
<t t-call="l10n_in.place_of_supply"/>
</xpath>
@ -15,45 +24,110 @@
<xpath expr="//div[@name='no_shipping']//t[@t-set='address']" position="inside">
<t t-call="l10n_in.place_of_supply"/>
</xpath>
<xpath expr="//div[@t-if='not is_html_empty(o.narration)']" position="before">
<t t-if="o.company_id.account_fiscal_country_id.code == 'IN'">
<p id="total_in_words" class="mb16">
<strong>Total (In Words): </strong>
<span t-field="o.amount_total_words"/>
</p>
</t>
</xpath>
<xpath expr="//table[@name='invoice_line_table']/thead/tr/th[1]" position="after">
<t t-if="o.company_id.account_fiscal_country_id.code == 'IN'">
<t t-if="o.company_id.account_fiscal_country_id.code == 'IN' and o.company_id.l10n_in_is_gst_registered">
<th>HSN/SAC</th>
</t>
</xpath>
<xpath expr="//t[@name='account_invoice_line_accountable']/td[1]" position="after">
<td t-if="o.company_id.account_fiscal_country_id.code == 'IN'">
<span t-if="line.product_id.l10n_in_hsn_code" t-field="line.product_id.l10n_in_hsn_code"></span>
<td t-if="o.company_id.account_fiscal_country_id.code == 'IN' and o.company_id.l10n_in_is_gst_registered">
<span t-if="line.l10n_in_hsn_code" t-field="line.l10n_in_hsn_code"></span>
</td>
</xpath>
<xpath expr="//h2" position="replace" >
<t t-if="o.company_id.account_fiscal_country_id.code == 'IN'">
<h2>
<span t-if="o.move_type == 'out_invoice' and o.state == 'posted'" t-field="o.journal_id.name"/>
<span t-if="o.move_type == 'out_invoice' and o.state == 'draft'">Draft <span t-field="o.journal_id.name"/></span>
<span t-if="o.move_type == 'out_invoice' and o.state == 'cancel'">Cancelled <span t-field="o.journal_id.name"/></span>
<span t-if="o.move_type == 'out_refund'">Credit Note</span>
<span t-if="o.move_type == 'in_refund'">Vendor Credit Note</span>
<span t-if="o.move_type == 'in_invoice'">Vendor Bill</span>
<span t-field="o.name"/>
</h2>
<xpath expr="//t[@name='account_invoice_line_accountable']/following-sibling::*[1]/td[1]" position="after">
<td t-if="o.company_id.account_fiscal_country_id.code == 'IN' and o.company_id.l10n_in_is_gst_registered"></td>
</xpath>
<xpath expr="//div[@id='qrcode_info']" position="attributes">
<attribute name="t-if" add="o.company_id.account_fiscal_country_id.code != 'IN'" separator="and"/>
</xpath>
<xpath expr="//div[@id='qrcode_info']" position="after">
<t t-if="o.company_id.account_fiscal_country_id.code == 'IN' and o.company_id.l10n_in_upi_id">
<div style="display:-webkit-flex;" class="flex-column">
<strong>PAYMENT QR CODE</strong>
<div class="mt-1 mb-1">
<p class="mb-0">UPI ID:</p>
<span class="mb-0" t-field="o.company_id.l10n_in_upi_id"/>
</div>
<div class="d-flex flex-row" t-attf-style="#{'-webkit-transform:translateX(-0.5rem);' if report_type != 'html' else '-webkit-transform:translate(-0.5rem,-0.8rem);'}">
<img src="/l10n_in/static/src/img/PhonePe-Logo.svg" style="width:4.5rem;"/>
<img src="/l10n_in/static/src/img/Google_Pay-Logo.svg" style="width:3.5rem;"/>
<img src="/l10n_in/static/src/img/Paytm-Logo.svg" style="width:4rem;"/>
<img src="/l10n_in/static/src/img/Upi-logo.svg" t-attf-style="#{'' if report_type != 'html' else 'padding:0.5rem;'} width:4rem;"/>
</div>
</div>
</t>
</xpath>
<t name="invoice_title" position="replace">
<t name="invoice_title">
<span t-out="o._get_l10n_in_invoice_label()"/>
</t>
</t>
<t name="proforma_invoice_title" position="replace">
<t name="proforma_invoice_title">
Proforma <span t-out="o._get_l10n_in_invoice_label()"/>
</t>
</t>
<xpath expr="//div[@id='payment_term']" position="after">
<t t-if="o.company_id.account_fiscal_country_id.code == 'IN' and o.company_id.l10n_in_is_gst_registered">
<t t-set="hsn_summary" t-value="o._l10n_in_get_hsn_summary_table()"/>
<t t-if="hsn_summary">
<div name="l10n_in_hsn_summary" class="mt-3" style="page-break-inside: avoid;">
<h4>HSN Summary</h4>
<table class="table table-sm table-borderless col-6">
<thead>
<th>HSN/SAC</th>
<th class="text-end">Quantity</th>
<th class="text-end">Rate %</th>
<th class="text-end">Taxable Value</th>
<th class="text-end" t-if="hsn_summary['has_gst']">SGST/UTGST</th>
<th class="text-end" t-if="hsn_summary['has_gst']">CGST</th>
<th class="text-end" t-if="hsn_summary['has_igst']">IGST</th>
<th class="text-end" t-if="hsn_summary['has_cess']">CESS</th>
</thead>
<tr t-foreach="hsn_summary['items']" t-as="item">
<td t-esc="item['l10n_in_hsn_code']"/>
<td class="text-end">
<span t-esc="item['quantity']"/>
<span t-if="hsn_summary['display_uom']">(<t t-esc="item['uom_name']"/>)</span>
</td>
<td class="text-end" t-esc="item['rate']"/>
<td class="text-end">
<span t-esc="item['amount_untaxed']"
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
</td>
<td class="text-end" t-if="hsn_summary['has_gst']">
<span t-esc="item['tax_amount_sgst']"
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
</td>
<td class="text-end" t-if="hsn_summary['has_gst']">
<span t-esc="item['tax_amount_cgst']"
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
</td>
<td class="text-end" t-if="hsn_summary['has_igst']">
<span t-esc="item['tax_amount_igst']"
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
</td>
<td class="text-end" t-if="hsn_summary['has_cess']">
<span t-esc="item['tax_amount_cess']"
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
</td>
</tr>
</table>
</div>
</t>
</t>
<t t-else="">$0</t>
</xpath>
</template>
<!-- Workarounds for Studio reports, see odoo/odoo#60660 -->
<!-- Workaround for Studio reports, see odoo/odoo#60660 -->
<template id="report_invoice" inherit_id="account.report_invoice">
<xpath expr='//t[@t-call="account.report_invoice_document"]' position="after">
<t t-elif="o._get_name_invoice_report() == 'l10n_in.l10n_in_report_invoice_document_inherit'"
@ -62,16 +136,8 @@
</xpath>
</template>
<template id="report_invoice_with_payments" inherit_id="account.report_invoice_with_payments">
<xpath expr='//t[@t-call="account.report_invoice_document"]' position="after">
<t t-elif="o._get_name_invoice_report() == 'l10n_in.l10n_in_report_invoice_document_inherit'"
t-call="l10n_in.l10n_in_report_invoice_document_inherit"
t-lang="lang"/>
</xpath>
</template>
<template id="place_of_supply">
<div t-if="o.l10n_in_state_id">
<div t-if="o.company_id.l10n_in_is_gst_registered and o.l10n_in_state_id">
Place of supply: <span t-out="o.l10n_in_state_id.name" />
</div>
</template>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- get vat from journal_id for all layout -->
<template id="l10n_in_external_layout" inherit_id="web.external_layout">
<xpath expr="//t[@t-if='company.external_report_layout_id']" position="before">
<t t-if="o and 'journal_id' in o and company.country_id.code == 'IN' and o.journal_id.l10n_in_gstin_partner_id.vat">
<t t-set="forced_vat" t-value="o.journal_id.l10n_in_gstin_partner_id.vat"/>
</t>
<t t-elif="o and 'l10n_in_journal_id' in o and company.country_id.code == 'IN' and o.l10n_in_journal_id.l10n_in_gstin_partner_id.vat">
<t t-set="forced_vat" t-value="o.l10n_in_journal_id.l10n_in_gstin_partner_id.vat"/>
</t>
</xpath>
</template>
</odoo>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_company_form" model="ir.ui.view">
<field name="name">res.company.form.inherit.l10n_in_upi</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='currency_id']" position="after">
<field name="l10n_in_upi_id" invisible="country_code != 'IN'"/>
</xpath>
<xpath expr="//label[@for='name']" position="attributes">
<attribute name="invisible">l10n_in_pan_type</attribute>
</xpath>
<xpath expr="//label[@for='name']" position="before">
<field class="o_form_label mb-0" name="l10n_in_pan_type" invisible="not l10n_in_pan_type"/>
</xpath>
<xpath expr="//field[@name='vat']" position="after">
<field name="l10n_in_pan_entity_id" invisible="country_code != 'IN'"/>
<field name="l10n_in_tan" invisible="not l10n_in_pan_entity_id"/>
</xpath>
<xpath expr="//sheet" position="before">
<div class="alert alert-warning mt-1 mb-1" role="alert" invisible="not l10n_in_gst_state_warning or country_code != 'IN'">
<field name="l10n_in_gst_state_warning"/>
<a name="action_update_state_as_per_gstin"
string="update it"
class="ms-1"
invisible="country_code != 'IN'"
type="object"/>
</div>
</xpath>
</field>
</record>
</odoo>

View file

@ -5,22 +5,150 @@
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
<field name="arch" type="xml">
<div id="invoicing_settings" position="inside">
<div class="col-xs-12 col-md-6 o_setting_box"
name="ecommerce_reseller_setting"
title="Manage Reseller(E-Commerce)"
attrs="{'invisible': [('country_code', '!=', 'IN')]}">
<div class="o_setting_left_pane">
<field name="group_l10n_in_reseller"/>
</div>
<div class="o_setting_right_pane" name="l10n_eu_service_right_pane">
<label for="group_l10n_in_reseller"/>
<div class="text-muted">
Use this if setup with Reseller(E-Commerce).
<block id="invoicing_settings" position="inside">
<setting help="Use this if setup with Reseller(E-Commerce)." name="ecommerce_reseller_setting" title="Manage Reseller(E-Commerce)" invisible="country_code != 'IN'">
<field name="group_l10n_in_reseller"/>
</setting>
</block>
<xpath expr="//block[@name='fiscal_localization_setting_container']" position="after">
<div id="india_integration_section">
<block title="Indian Integration" id="india_localization" invisible="country_code != 'IN'">
<setting name="l10n_in_tds"
help="Enable this to activate the Tax Deducted at Source (TDS) feature and the account-based TDS section suggestion."
company_dependent="1"
string="TDS"
documentation="/applications/finance/fiscal_localizations/india.html">
<field name="l10n_in_tds_feature"/>
<div class="content-group" invisible="not l10n_in_tds_feature">
<div class="row mt8">
<label for="l10n_in_tan" class="col-lg-5 o_light_label" string="TAN"/>
<field name="l10n_in_tan" required="l10n_in_tds_feature"/>
</div>
</div>
</setting>
<setting name="l10n_in_tcs"
help="Enable this to activate the Tax Collected at Source (TCS) feature and the account-based TCS section suggestion."
company_dependent="1"
string="TCS"
documentation="/applications/finance/fiscal_localizations/india.html">
<field name="l10n_in_tcs_feature"/>
</setting>
<setting string="Registered Under GST"
company_dependent="1"
help="Enable this if you have GST number except composition scheme.">
<field name="l10n_in_is_gst_registered"/>
<div invisible="not l10n_in_is_gst_registered">
<div id="india_gst_number" class="row">
<label string="GST Number" for="l10n_in_gstin" class="col-lg-4 o_light_label"/>
<field name="l10n_in_gstin" required="l10n_in_is_gst_registered"/>
</div>
</div>
</setting>
<t invisible="not l10n_in_is_gst_registered">
<setting id="gsp_selection" string="GSP" help="Select BVM as GSP Tera is Deprecated" documentation="/applications/finance/fiscal_localizations/india.html">
<div class="text-muted">
Changes to this configuration will be automatically applied to all Indian companies.
Please refer to the <a href="https://www.odoo.com/documentation/16.0/applications/finance/fiscal_localizations/india.html#nic-e-invoice-registration">documentation</a> for detailed guidance on accurate configuration.
<div class="text-warning" invisible="l10n_in_gsp == 'bvm'">
The current GSP is scheduled for deprecation soon.
We strongly recommend switching to BVM GSP at the earliest to ensure uninterrupted service.
</div>
</div>
<div class="content-group">
<div class="mt16 row">
<field name="l10n_in_gsp" nolabel="1"/>
</div>
</div>
</setting>
<setting name="india_production_setting"
company_dependent="1"
string="Production Environment"
help="Activate this to start using Indian services in the production environment."
groups="base.group_no_one">
<field name="l10n_in_edi_production_env"/>
<div class='mt8'
invisible="not l10n_in_edi_production_env or (not module_l10n_in_edi and not l10n_in_gstin_status_feature and not module_l10n_in_ewaybill and not l10n_in_gst_efiling_feature)">
<button name="l10n_in_edi_buy_iap"
title="Costs 1 credit per transaction. Free 200 credits will be available for the first time."
icon="fa-arrow-right"
type="object"
string="Buy credits"
class="btn-link"/>
</div>
</setting>
<setting name="electronic_invoices_in"
help="Connect to NIC (National Informatics Center) to submit invoices on posting."
company_dependent="1"
string="E-Invoicing"
documentation="/applications/finance/fiscal_localizations/india.html#india-e-invoicing">
<field name="module_l10n_in_edi"/>
<div class="content-group" invisible="not module_l10n_in_edi">
<div class="text-warning mt16 mb4">
Save this page and come back here to set up the feature.
</div>
</div>
</setting>
<setting name="electronic_waybill_in"
help="Connect to NIC (National Informatics Center) to submit e-waybill on posting."
company_dependent="1"
string="E-Way bill"
documentation="/applications/finance/fiscal_localizations/india.html#india-e-waybill">
<field name="module_l10n_in_ewaybill" class="oe_inline"/>
<div class="content-group" invisible="not module_l10n_in_ewaybill">
<div class="text-warning mt16 mb4">
Save this page and come back here to set up the feature.
</div>
</div>
</setting>
<setting name="india_gstr"
help="Connect to GST portal to submit GSTR-1 and get GSTR-2B"
company_dependent="1"
documentation="/applications/finance/fiscal_localizations/india.html#india-gstr">
<field name="l10n_in_gst_efiling_feature" widget="upgrade_boolean"/>
</setting>
<setting name="india_gstin_status_api_settings"
string="Check GST Number Status"
company_dependent="1"
help="Enable this to check the GST Number status"
documentation="/applications/finance/fiscal_localizations/india.html#indian-check-gstin-status">
<field name="l10n_in_gstin_status_feature" class="oe_inline"/>
</setting>
<setting name="india_gstr_api"
help="This feature helps you to quickly create draft vendor bills using the data your vendor submitted during E-invoicing on the GST portal."
company_dependent="1">
<field name="l10n_in_fetch_vendor_edi_feature" widget="upgrade_boolean"/>
</setting>
<setting string="HSN/SAC Validation"
help="HSN/SAC Digit Validation for GST Compliance based on your Aggregate Annual Turnover (AATO)."
company_dependent="1">
<field name="l10n_in_hsn_code_digit"/>
</setting>
</t>
</block>
</div>
</xpath>
<block id="print_vendor_checks_setting_container" position="inside">
<setting id="vendor_payment_order_file"
company_dependent="1"
help="Generate Vendor Payment Order file(csv file), upload to your bank to make the payments"
invisible="country_code != 'IN'">
<field name="l10n_in_enet_vendor_batch_payment_feature" widget="upgrade_boolean"/>
</setting>
</block>
<block id="default_accounts" position="inside">
<setting string="India TDS Control:" invisible="not l10n_in_tds_feature">
<div class="content-group">
<div class="row mt8">
<label for="l10n_in_withholding_journal_id" class="col-lg-5 o_light_label" string="Journal"/>
<field name="l10n_in_withholding_journal_id" domain="[('type', '=', 'general')]"/>
</div>
<div class="row mt8">
<label for="l10n_in_withholding_account_id" class="col-lg-5 o_light_label" string="Account"/>
<field name="l10n_in_withholding_account_id"/>
</div>
</div>
</div>
</div>
</setting>
</block>
</field>
</record>
</odoo>

View file

@ -7,7 +7,18 @@
<field name="inherit_id" ref="base.view_country_state_form"/>
<field name="arch" type="xml">
<field name="code" position="after">
<field name="l10n_in_tin" attrs="{'invisible': [('country_id', '!=', %(base.in)d)]}"/>
<field name="l10n_in_tin" invisible="country_id != %(base.in)d"/>
</field>
</field>
</record>
<record id="l10n_in_view_country_state_tree_inherit" model="ir.ui.view">
<field name="name">l10n.in.res.country.state.list.inhert</field>
<field name="model">res.country.state</field>
<field name="inherit_id" ref="base.view_country_state_tree"/>
<field name="arch" type="xml">
<field name="code" position="after">
<field name="l10n_in_tin" groups="base.group_no_one" />
</field>
</field>
</record>

View file

@ -4,13 +4,82 @@
<field name="name">l10n.in.res.partner.vat.inherit</field>
<field name="model">res.partner</field>
<field name="priority" eval="90"/>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='vat']" position="attributes">
<attribute name="attrs">{'required':[('l10n_in_gst_treatment', 'in', ['regular', 'composition', 'special_economic_zone', 'deemed_export'])], 'readonly': [('parent_id', '!=', False)]}</attribute>
<attribute name="required">l10n_in_is_gst_registered_enabled and l10n_in_gst_treatment in ['regular', 'composition', 'special_economic_zone', 'deemed_export']</attribute>
</xpath>
<xpath expr="//field[@name='vat']" position="before">
<field name="l10n_in_gst_treatment" attrs="{'readonly': [('parent_id', '!=', False)]}"/>
<field name="l10n_in_gst_treatment" invisible="'IN' not in fiscal_country_codes or not l10n_in_is_gst_registered_enabled" readonly="parent_id"/>
</xpath>
<xpath expr="//field[@name='vat']" position="after">
<field name="l10n_in_pan_entity_id" invisible="country_code != 'IN'" readonly="parent_id"/>
<field name="l10n_in_tan" invisible="country_code != 'IN' or not l10n_in_pan_entity_id" readonly="parent_id"/>
</xpath>
<xpath expr="//sheet" position="before">
<div class="alert alert-warning" role="alert"
invisible="not display_pan_warning">
PAN number is not same as the 3rd to 12th characters of the GST number.
</div>
<div class="alert alert-warning mt-1 mb-1" role="alert" invisible="not l10n_in_gst_state_warning or country_code != 'IN'">
<field name="l10n_in_gst_state_warning"/>
<a name="action_update_state_as_per_gstin"
string="update it"
class="ms-1"
invisible="country_code != 'IN'"
type="object"/>
</div>
</xpath>
</field>
</record>
<record id="l10n_in_view_partner_tree" model="ir.ui.view">
<field name="name">l10n.in.res.partner.tree</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='vat']" position="after">
<field name="l10n_in_pan_entity_id" optional="hide"/>
</xpath>
</field>
</record>
<record id="l10n_in_view_res_partner_filter" model="ir.ui.view">
<field name="name">l10n.in.view.res.partner.filter.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="l10n_in_pan_entity_id"/>
</field>
<filter name="group_country" position="after">
<filter name="l10n_in_pan_entity_id" string="PAN" context="{'group_by': 'l10n_in_pan_entity_id'}"/>
</filter>
</field>
</record>
<record id="l10n_in_view_partner_base_vat_form" model="ir.ui.view">
<field name="name">l10n.in.gstin.status.view.partner.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base_vat.view_partner_base_vat_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='vies_valid']" position="after">
<span invisible="country_code != 'IN' or not l10n_in_gstin_status_feature_enabled or not vat or 'IN' not in fiscal_country_codes">
<span invisible="not l10n_in_gstin_verified_date or not l10n_in_gstin_verified_status"
class="oe_inline text-success">Active</span>
<span invisible="not l10n_in_gstin_verified_date or l10n_in_gstin_verified_status"
class="oe_inline text-danger">Inactive</span>
<span invisible="not l10n_in_gstin_verified_date and not l10n_in_gstin_verified_status" class="text-muted">
(
<field name="l10n_in_gstin_verified_date" widget="remaining_days" class="oe_inline" readonly="1" />
<button name="action_l10n_in_verify_gstin_status" type="object" icon="fa-refresh"
class="oe_link p-0 ps-2" title="Reverify GSTIN status" />
)
</span>
<button string="Check Status" name="action_l10n_in_verify_gstin_status" type="object"
icon="fa-check" class="oe_link p-0" title="Check GSTIN status"
invisible="l10n_in_gstin_verified_date" />
</span>
</xpath>
</field>
</record>

View file

@ -5,19 +5,8 @@
<field name="model">uom.uom</field>
<field name="inherit_id" ref="uom.product_uom_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='category_id']" position="after">
<field name="l10n_in_code"/>
</xpath>
</field>
</record>
<record id="product_uom_categ_form_view_inherit_l10n_in" model="ir.ui.view">
<field name="name">uom.category.form</field>
<field name="model">uom.category</field>
<field name="inherit_id" ref="uom.product_uom_categ_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='uom_ids']/tree/field[@name='name']" position="after">
<field name="l10n_in_code"/>
<xpath expr="//field[@name='name']" position="after">
<field name="l10n_in_code" invisible="'IN' not in fiscal_country_codes"/>
</xpath>
</field>
</record>

View file

@ -0,0 +1 @@
from . import l10n_in_withhold_wizard

View file

@ -0,0 +1,315 @@
from markupsafe import Markup
from odoo import _, api, Command, fields, models
from odoo.exceptions import ValidationError, UserError
from odoo.tools import float_compare
class L10n_InWithholdWizard(models.TransientModel):
_name = 'l10n_in.withhold.wizard'
_description = "Withhold Wizard"
_check_company_auto = True
@api.model
def default_get(self, fields):
result = super().default_get(fields)
active_model = self.env.context.get('active_model')
active_ids = self.env.context.get('active_ids', [])
if active_model not in ('account.move', 'account.payment') or not active_ids:
raise UserError(_("TDS must be created from an Invoice or a Payment."))
if len(active_ids) > 1:
raise UserError(_("You can only create a withhold for only one record at a time."))
active_record = self.env[active_model].browse(active_ids)
result['reference'] = _("TDS of %s", active_record.name)
if active_model == 'account.move':
if active_record.move_type not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund') or active_record.state != 'posted':
raise UserError(_("TDS must be created from Posted Customer Invoices, Customer Credit Notes, Vendor Bills or Vendor Refunds."))
result['related_move_id'] = active_record.id
elif active_model == 'account.payment':
if not active_record.partner_id:
type_name = _("Vendor Payment") if active_record.partner_type == 'supplier' else _("Customer Payment")
raise UserError(_("Please set a partner on the %s before creating a withhold.", type_name))
result['related_payment_id'] = active_record.id
return result
reference = fields.Char(string="Reference")
type_name = fields.Char(string="Type", compute='_compute_type_name')
related_move_id = fields.Many2one(
comodel_name='account.move',
string="Invoice/Bill",
readonly=True,
)
related_payment_id = fields.Many2one(
comodel_name='account.payment',
string="Payment",
readonly=True,
)
tds_deduction = fields.Selection(
selection=[
('normal', 'Normal Deduction'),
('lower', 'Lower Deduction'),
('higher', 'Higher Deduction'),
('no', 'No Deduction'),
],
string="TDS Deduction",
compute='_compute_tds_deduction',
)
company_id = fields.Many2one(
comodel_name='res.company',
string="Company",
compute='_compute_company_id'
)
currency_id = fields.Many2one(
related='company_id.currency_id',
string="Currency",
)
journal_id = fields.Many2one(
comodel_name='account.journal',
string="Journal",
compute='_compute_journal', precompute=True,
readonly=False, store=True,
required=True,
check_company=True,
)
date = fields.Date(
string="Date",
default=fields.Date.context_today,
)
l10n_in_tds_tax_type = fields.Char(
string="Indian Tax Type",
compute='_compute_l10n_in_tds_tax_type'
)
l10n_in_withholding_warning = fields.Json(string="Withholding warning", compute='_compute_l10n_in_withholding_warning')
base = fields.Monetary(string="Base Amount", compute='_compute_base', store=True, readonly=False)
tax_id = fields.Many2one(
comodel_name='account.tax',
string="TDS Section",
required=True,
compute='_compute_tax_id',
store=True,
readonly=False,
)
amount = fields.Monetary(
string="TDS Amount",
compute='_compute_amount',
)
# ===== Constraints =====
@api.constrains('base')
def _check_amounts(self):
for wizard in self:
if wizard.currency_id.compare_amounts(wizard.base, 0.0) <= 0:
raise ValidationError(_("Negative or zero values are not allowed in Base Amount for withhold"))
if wizard.currency_id.compare_amounts(wizard.amount, 0.0) <= 0:
raise ValidationError(_("Negative or zero values are not allowed in TDS Amount for withhold"))
# ===== Computes =====
@api.depends('related_move_id', 'related_payment_id')
def _compute_l10n_in_tds_tax_type(self):
for wizard in self:
withhold_type = wizard._get_withhold_type()
l10n_in_tds_tax_type = False
if withhold_type in ('in_withhold', 'in_refund_withhold'):
l10n_in_tds_tax_type = 'tds_purchase'
elif withhold_type in ('out_withhold', 'out_refund_withhold'):
l10n_in_tds_tax_type = 'tds_sale'
wizard.l10n_in_tds_tax_type = l10n_in_tds_tax_type
@api.depends('related_move_id', 'related_payment_id')
def _compute_tds_deduction(self):
for wizard in self:
related_partner = wizard.related_move_id.commercial_partner_id if wizard.related_move_id else wizard.related_payment_id.partner_id.commercial_partner_id
wizard.tds_deduction = related_partner.l10n_in_pan_entity_id.tds_deduction if related_partner and related_partner.l10n_in_pan_entity_id else 'higher'
@api.depends('related_move_id', 'related_payment_id')
def _compute_type_name(self):
for wizard in self:
if wizard.related_payment_id:
wizard.type_name = _("Vendor Payment") if wizard.related_payment_id.partner_type == 'supplier' else _("Customer Payment")
else:
wizard.type_name = wizard.related_move_id.type_name
@api.depends('related_move_id', 'related_payment_id')
def _compute_company_id(self):
for wizard in self:
wizard.company_id = wizard.related_move_id.company_id or wizard.related_payment_id.company_id
@api.depends('company_id')
def _compute_journal(self):
for wizard in self:
wizard.journal_id = wizard.company_id.parent_ids.l10n_in_withholding_journal_id[-1:] or \
wizard.env['account.journal'].search([*self.env['account.journal']._check_company_domain(wizard.company_id), ('type', '=', 'general')], limit=1)
@api.depends('related_payment_id', 'related_move_id', 'l10n_in_tds_tax_type', 'base', 'tax_id')
def _compute_l10n_in_withholding_warning(self):
for wizard in self:
warnings = {}
if wizard.tax_id and wizard.l10n_in_tds_tax_type == 'tds_purchase' and not wizard.related_move_id.commercial_partner_id.l10n_in_pan_entity_id:
warnings['lower_tds_tax'] = {
'message': _("Please deduct TDS at higher rate if PAN is missing. Ignore if already applied.")
}
precision = self.currency_id.decimal_places
if wizard.related_move_id and float_compare(wizard.related_move_id.amount_untaxed, wizard.base, precision_digits=precision) < 0:
message = _("The base amount of TDS is greater than the amount of the %s", wizard.type_name)
warnings['lower_move_amount'] = {
'message': message
}
wizard.l10n_in_withholding_warning = warnings
@api.depends('related_move_id', 'related_payment_id')
def _compute_tax_id(self):
for wizard in self:
sections = wizard.related_move_id._get_l10n_in_tds_tcs_applicable_sections()
if sections:
accounts_by_section = {}
for line in wizard.related_move_id.line_ids:
section = line.account_id.l10n_in_tds_tcs_section_id
if section in sections:
accounts_by_section[section] = line.account_id
tax = self.env['account.tax']
for section, account in accounts_by_section.items():
# Search for the last withhold move line that matches the pan entity and account and section
withhold_move_line = self.env['account.move.line'].search([
('move_id.l10n_in_withholding_ref_move_id.commercial_partner_id.l10n_in_pan_entity_id', '=', wizard.related_move_id.commercial_partner_id.l10n_in_pan_entity_id.id),
('move_id.l10n_in_withholding_ref_move_id.line_ids.account_id', 'in', account.id),
('move_id.l10n_in_withholding_ref_move_id.line_ids.l10n_in_tds_tcs_section_id', 'in', section.id),
('move_id.state', '=', 'posted'),
('tax_ids.l10n_in_section_id', '=', section.id),
], limit=1, order='id desc')
if withhold_move_line:
tax = withhold_move_line.tax_ids.filtered(lambda t: t.l10n_in_section_id == section)
break
if tax:
wizard.tax_id = tax
@api.depends('tax_id')
def _compute_base(self):
for wizard in self:
if (
wizard.tax_id and
wizard.related_move_id and
wizard.tax_id.l10n_in_section_id not in (self.env.ref('l10n_in.tds_section_194q'), self.env.ref('l10n_in.tds_section_194n'))
):
sign = -1 if wizard.related_move_id.is_inbound() else 1
wizard.base = sign * sum(wizard.related_move_id.line_ids.filtered(lambda l: l.account_id.l10n_in_tds_tcs_section_id == wizard.tax_id.l10n_in_section_id).mapped('balance'))
@api.depends('tax_id', 'base')
def _compute_amount(self):
for wizard in self:
tax_amount = 0.0
if wizard.tax_id:
taxes_res = wizard.tax_id.compute_all(
wizard.base,
currency=wizard.tax_id.company_id.currency_id,
quantity=1.0,
product=False,
partner=False,
is_refund=False,
)
tax_amount = taxes_res['total_included'] - taxes_res['total_excluded']
wizard.amount = abs(tax_amount)
def _get_withhold_type(self):
if self.related_move_id:
move_type = self.related_move_id.move_type
withhold_type = {
'out_invoice': 'out_withhold',
'in_invoice': 'in_withhold',
'out_refund': 'out_refund_withhold',
'in_refund': 'in_refund_withhold',
}[move_type]
else:
withhold_type = 'in_withhold' if self.related_payment_id.partner_type == 'supplier' else 'out_withhold'
return withhold_type
# ===== MOVE CREATION METHODS =====
def action_create_and_post_withhold(self):
self.ensure_one()
withholding_account_id = self.company_id.l10n_in_withholding_account_id
self._validate_withhold_data_on_post(withholding_account_id)
# Withhold creation and posting
vals = self._prepare_withhold_header()
move_lines = self._prepare_withhold_move_lines(withholding_account_id)
vals['line_ids'] = [Command.create(line) for line in move_lines]
withhold = self.with_company(self.company_id).env['account.move'].create(vals)
withhold.action_post()
# If the withhold is created from a payment, there is no need to reconcile
if not self.related_payment_id:
wh_reconc = withhold.line_ids.filtered(
lambda l: l.account_id.account_type in ('asset_receivable', 'liability_payable'))
inv_reconc = self.related_move_id.line_ids.filtered(
lambda l: l.account_id.account_type in ('asset_receivable', 'liability_payable') and not l.reconciled)
(inv_reconc + wh_reconc).reconcile()
related_record = self.related_move_id or self.related_payment_id
withhold._message_log(
body=Markup("%s %s: <a href='#' data-oe-model='%s' data-oe-id='%s'>%s</a>") % (
_("TDS created from"),
self.type_name,
related_record._name,
related_record.id,
related_record.name
))
return withhold
def _prepare_withhold_header(self):
""" Prepare the header for the withhold entry """
vals = {
'date': self.date,
'journal_id': self.journal_id.id,
'partner_id': self.related_move_id.partner_id.id or self.related_payment_id.partner_id.id,
'move_type': 'entry',
'ref': self.reference,
'l10n_in_is_withholding': True,
'l10n_in_withholding_ref_move_id': self.related_move_id.id or self.related_payment_id.move_id.id,
'l10n_in_withholding_ref_payment_id': self.related_payment_id.id
}
return vals
def _prepare_withhold_move_lines(self, withholding_account_id):
"""
Prepare the move lines for the withhold entry
"""
def append_vals(quantity, price_unit, debit, credit, account_id, tax_ids):
return {
'quantity': quantity,
'price_unit': price_unit,
'debit': debit,
'credit': credit,
'account_id': account_id.id,
'tax_ids': tax_ids,
}
vals = []
partner = self.related_move_id.partner_id or self.related_payment_id.partner_id
withhold_type = self._get_withhold_type()
if withhold_type in ('in_withhold', 'in_refund_withhold'):
partner_account = partner.property_account_payable_id
else:
partner_account = partner.property_account_receivable_id
# Create move line for withholding tax and the base amount
debit = self.base if withhold_type in ('in_withhold', 'out_refund_withhold') else 0.0
credit = 0.0 if withhold_type in ('in_withhold', 'out_refund_withhold') else self.base
vals.append(append_vals(1.0, self.base, debit, credit, withholding_account_id, [Command.set(self.tax_id.ids)]))
total_amount = self.base
total_tax = self.amount
# Create move line for the base amount
debit = 0.0 if withhold_type in ('in_withhold', 'out_refund_withhold') else total_amount
credit = total_amount if withhold_type in ('in_withhold', 'out_refund_withhold') else 0.0
vals.append(append_vals(1.0, total_amount, debit, credit, withholding_account_id, False))
# Create move line for the tax amount
debit = total_tax if withhold_type in ('in_withhold', 'out_refund_withhold') else 0.0
credit = 0.0 if withhold_type in ('in_withhold', 'out_refund_withhold') else total_tax
vals.append(append_vals(1.0, total_tax, debit, credit, partner_account, False))
return vals
def _validate_withhold_data_on_post(self, withholding_account_id):
if not withholding_account_id:
raise UserError(_("Please configure the withholding account from the settings"))

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="l10n_in_withholding_entry_form_action" model="ir.actions.act_window">
<field name="name">Create TDS Entry</field>
<field name="res_model">l10n_in.withhold.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record id="tds_entry_view_form" model="ir.ui.view">
<field name="name">l10n_in.withhold.wizard.view.form</field>
<field name="model">l10n_in.withhold.wizard</field>
<field name="arch" type="xml">
<form>
<div class="alert alert-warning mt-1 mb-1" role="alert" invisible="not l10n_in_withholding_warning">
<div>
<field name="l10n_in_withholding_warning" widget="actionable_errors"/>
</div>
</div>
<sheet>
<group>
<group id="header_left_group">
<field name="date" string="Entry Date"/>
<label for="tax_id" string="TDS Section"/>
<div>
<field name="tax_id" class="oe_inline" domain="[('l10n_in_tax_type', '=', l10n_in_tds_tax_type)]" required="True"
options="{'no_create': True, 'no_open': True}"/>
<field name="tds_deduction" class="oe_inline"
decoration-success="tds_deduction == 'lower'"
decoration-danger="tds_deduction == 'higher'"
decoration-warning="tds_deduction == 'no'"
invisible="tds_deduction == 'normal'"
/>
</div>
<field name="base" widget="monetary" options="{'currency_field': 'currency_id'}" required="True"/>
<field name="amount" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="currency_id" invisible="1"/> <!-- used to display the currency symbol -->
<field name="related_move_id" invisible="1"/> <!-- used to compute the l10n_in_tds_tax_type -->
<field name="related_payment_id" invisible="1"/> <!-- used to compute the l10n_in_tds_tax_type -->
</group>
<group id="header_right_group">
<field name="journal_id" domain="[('type', '=', 'general')]"/>
<field name="reference"/>
</group>
</group>
</sheet>
<footer>
<button string="Apply TDS" type="object" name="action_create_and_post_withhold" class="btn-primary"/>
<button string="Discard" special="cancel" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
</odoo>

View file

@ -1,13 +1,18 @@
[project]
name = "odoo-bringout-oca-ocb-l10n_in"
version = "16.0.0"
description = "Indian - Accounting - Odoo addon"
description = "Indian - Accounting -
Odoo addon
"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-ocb-account_tax_python>=16.0.0",
"odoo-bringout-oca-ocb-base_vat>=16.0.0",
"odoo-bringout-oca-ocb-account_tax_python>=19.0.0",
"odoo-bringout-oca-ocb-base_vat>=19.0.0",
"odoo-bringout-oca-ocb-account_debit_note>=19.0.0",
"odoo-bringout-oca-ocb-account>=19.0.0",
"odoo-bringout-oca-ocb-iap>=19.0.0",
"requests>=2.25.1"
]
readme = "README.md"
@ -17,7 +22,7 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]