diff --git a/addons/l10n_in/__manifest__.py b/addons/l10n_in/__manifest__.py index cd12e728b5e..324703939fa 100644 --- a/addons/l10n_in/__manifest__.py +++ b/addons/l10n_in/__manifest__.py @@ -18,7 +18,7 @@ Sheet, now only Vertical format has been permitted Which is Supported By Odoo. """, 'category': 'Accounting/Localizations', 'depends': [ - 'account_tax_python', + 'account_tax_python', 'base_vat', ], 'data': [ 'security/l10n_in_security.xml', diff --git a/addons/l10n_in/models/account.py b/addons/l10n_in/models/account.py index eb10eced9b9..bdc6e36d0a3 100644 --- a/addons/l10n_in/models/account.py +++ b/addons/l10n_in/models/account.py @@ -9,8 +9,7 @@ class AccountJournal(models.Model): _inherit = "account.journal" # Use for filter import and export type. - l10n_in_import_export = fields.Boolean("Import/Export", help="Tick this if this journal is use for Import/Export Under Indian GST.") - l10n_in_gstin_partner_id = fields.Many2one('res.partner', string="GSTIN", ondelete="restrict", help="GSTIN related to this journal. If empty then consider as company GSTIN.") + 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): """ diff --git a/addons/l10n_in/models/account_invoice.py b/addons/l10n_in/models/account_invoice.py index 9a8a8496803..5f7b203d0f3 100644 --- a/addons/l10n_in/models/account_invoice.py +++ b/addons/l10n_in/models/account_invoice.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError class AccountMove(models.Model): @@ -13,21 +14,43 @@ class AccountMove(models.Model): invoice.amount_total_words = invoice.currency_id.amount_to_text(invoice.amount_total) amount_total_words = fields.Char("Total (In Words)", compute="_compute_amount_total_words") - # Use for invisible fields in form views. - l10n_in_import_export = fields.Boolean(related='journal_id.l10n_in_import_export', readonly=True) + l10n_in_gst_treatment = fields.Selection([ + ('regular', 'Registered Business - Regular'), + ('composition', 'Registered Business - Composition'), + ('unregistered', 'Unregistered Business'), + ('consumer', 'Consumer'), + ('overseas', 'Overseas'), + ('special_economic_zone', 'Special Economic Zone'), + ('deemed_export', 'Deemed Export') + ], string="GST Treatment", readonly=True, states={'draft': [('readonly', False)]}) + l10n_in_state_id = fields.Many2one('res.country.state', string="Location of supply") + l10n_in_company_country_code = fields.Char(related='company_id.country_id.code', string="Country code") + l10n_in_gstin = fields.Char(string="GSTIN") # For Export invoice this data is need in GSTR report - l10n_in_export_type = fields.Selection([ - ('regular', 'Regular'), ('deemed', 'Deemed'), - ('sale_from_bonded_wh', 'Sale from Bonded WH'), - ('export_with_igst', 'Export with IGST'), - ('sez_with_igst', 'SEZ with IGST payment'), - ('sez_without_igst', 'SEZ without IGST payment')], - string='Export Type', default='regular', required=True) 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', 'Shipping port code', states={'draft': [('readonly', False)]}) + l10n_in_shipping_port_code_id = fields.Many2one('l10n_in.port.code', 'Port code', 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_partner_vat = fields.Char(related="partner_id.vat", readonly=True) + + @api.onchange('partner_id') + def _onchange_partner_id(self): + """Use journal type to define document type because not miss state in any entry including POS entry""" + if self.l10n_in_company_country_code == 'IN': + self.l10n_in_gst_treatment = self.partner_id.l10n_in_gst_treatment + return super()._onchange_partner_id() + + @api.model + def _l10n_in_get_indian_state(self, partner): + """In tax return filing, If customer is not Indian in that case place of supply is must set to Other Territory. + So we set Other Territory in l10n_in_state_id when customer(partner) is not Indian + Also we raise if state is not set in Indian customer. + State is big role under GST because tax type is depend on.for more information check this https://www.cbic.gov.in/resources//htdocs-cbec/gst/Integrated%20goods%20&%20Services.pdf""" + if partner.country_id and partner.country_id.code == 'IN' and not partner.state_id: + raise ValidationError(_("State is missing from address in '%s'. First set state after post this invoice again." %(partner.name))) + elif partner.country_id and partner.country_id.code != 'IN': + return self.env.ref('l10n_in.state_in_ot') + return partner.state_id + @api.model def _get_tax_grouping_key_from_tax_line(self, tax_line): @@ -53,3 +76,39 @@ class AccountMove(models.Model): line.product_id.id, ] return tax_key + + def _l10n_in_get_shipping_partner(self): + """Overwrite in sale""" + self.ensure_one() + return self.partner_id + + @api.model + def _l10n_in_get_shipping_partner_gstin(self, shipping_partner): + """Overwrite in sale""" + return shipping_partner.vat + + def post(self): + """Use journal type to define document type because not miss state in any entry including POS entry""" + res = super().post() + gst_treatment_name_mapping = {k: v for k, v in + self._fields['l10n_in_gst_treatment']._description_selection(self.env)} + for move in self.filtered(lambda m: m.l10n_in_company_country_code == 'IN'): + """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 + if not company_unit_partner.state_id: + raise ValidationError(_("State is missing from your company/unit %s(%s).\nFirst set state in your company/unit." % (company_unit_partner.name, company_unit_partner.id))) + elif self.journal_id.type == 'purchase': + move.l10n_in_state_id = company_unit_partner.state_id + + shipping_partner = move._l10n_in_get_shipping_partner() + move.l10n_in_gstin = move._l10n_in_get_shipping_partner_gstin(shipping_partner) + if not move.l10n_in_gstin and move.l10n_in_gst_treatment in ['regular', 'composition', 'special_economic_zone', 'deemed_export']: + raise ValidationError(_("Partner %s(%s) GSTIN is required under GST Treatment %s" % (shipping_partner.name, shipping_partner.id, gst_treatment_name_mapping.get(move.l10n_in_gst_treatment)))) + if self.journal_id.type == 'sale': + move.l10n_in_state_id = self._l10n_in_get_indian_state(shipping_partner) + if not move.l10n_in_state_id: + move.l10n_in_state_id = self._l10n_in_get_indian_state(move.partner_id) + #still state is not set then assumed that transaction is local like PoS so set state of company unit + if not move.l10n_in_state_id: + move.l10n_in_state_id = company_unit_partner.state_id + return res diff --git a/addons/l10n_in/models/chart_template.py b/addons/l10n_in/models/chart_template.py index 39f3270b755..cb18bb758a1 100644 --- a/addons/l10n_in/models/chart_template.py +++ b/addons/l10n_in/models/chart_template.py @@ -15,11 +15,6 @@ class AccountChartTemplate(models.Model): journal['l10n_in_gstin_partner_id'] = company.partner_id.id if journal['code'] == 'INV': journal['name'] = _('Tax Invoices') - - res += [ - {'type': 'sale', 'name': _('Retail Invoices'), 'code': 'RETINV', 'company_id': company.id, 'show_on_dashboard': True, 'l10n_in_gstin_partner_id': company.partner_id.id}, - {'type': 'sale', 'name': _('Export Invoices'), 'code': 'EXPINV', 'company_id': company.id, 'show_on_dashboard': True, 'l10n_in_import_export': True, 'l10n_in_gstin_partner_id': company.partner_id.id} - ] return res class AccountTaxTemplate(models.Model): diff --git a/addons/l10n_in/models/res_partner.py b/addons/l10n_in/models/res_partner.py index d1c5a27d9a9..f14e39536a3 100644 --- a/addons/l10n_in/models/res_partner.py +++ b/addons/l10n_in/models/res_partner.py @@ -2,16 +2,45 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ -from odoo.exceptions import ValidationError class ResPartner(models.Model): _inherit = 'res.partner' - # Use in view attrs. Need to required state_id if Country is India. - l10n_in_country_code = fields.Char(related="country_id.code", string="Country code") + l10n_in_gst_treatment = fields.Selection([ + ('regular', 'Registered Business - Regular'), + ('composition', 'Registered Business - Composition'), + ('unregistered', 'Unregistered Business'), + ('consumer', 'Consumer'), + ('overseas', 'Overseas'), + ('special_economic_zone', 'Special Economic Zone'), + ('deemed_export', 'Deemed Export'), + ], string="GST Treatment") - @api.constrains('vat', 'country_id') - def l10n_in_check_vat(self): - for partner in self.filtered(lambda p: p.commercial_partner_id.country_id.code == 'IN' and p.vat and len(p.vat) != 15): - raise ValidationError(_('The GSTIN [%s] for partner [%s] should be 15 characters only.') % (partner.vat, partner.name)) + @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' + 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' + return res + + @api.onchange('vat') + def onchange_vat(self): + if self.vat and self.check_vat_in(self.vat): + state_id = self.env['res.country.state'].search([('l10n_in_tin', '=', self.vat[:2])], limit=1) + if state_id: + self.state_id = state_id + + @api.model + def _commercial_fields(self): + res = super()._commercial_fields() + return res + ['l10n_in_gst_treatment'] diff --git a/addons/l10n_in/report/account_invoice_report.py b/addons/l10n_in/report/account_invoice_report.py index 5853d298343..5f59a4497aa 100644 --- a/addons/l10n_in/report/account_invoice_report.py +++ b/addons/l10n_in/report/account_invoice_report.py @@ -16,12 +16,15 @@ class L10nInAccountInvoiceReport(models.Model): name = fields.Char(string="Invoice Number") partner_id = fields.Many2one('res.partner', string="Customer") is_reverse_charge = fields.Char("Reverse Charge") - l10n_in_export_type = fields.Selection([ - ('regular', 'Regular'), ('deemed', 'Deemed'), - ('sale_from_bonded_wh', 'Sale from Bonded WH'), - ('export_with_igst', 'Export with IGST'), - ('sez_with_igst', 'SEZ with IGST payment'), - ('sez_without_igst', 'SEZ without IGST payment')]) + l10n_in_gst_treatment = fields.Selection([ + ('regular', 'Registered Business - Regular'), + ('composition', 'Registered Business - Composition'), + ('unregistered', 'Unregistered Business'), + ('consumer', 'Consumer'), + ('overseas', 'Overseas'), + ('special_economic_zone', 'Special Economic Zone'), + ('deemed_export', 'Deemed Export'), + ], string="GST Treatment") journal_id = fields.Many2one('account.journal', string="Journal") state = fields.Selection([('draft', 'Unposted'), ('posted', 'Posted')], string='Status') igst_amount = fields.Float(string="IGST Amount") @@ -70,7 +73,7 @@ class L10nInAccountInvoiceReport(models.Model): sub.state, sub.partner_id, sub.date, - sub.l10n_in_export_type, + sub.l10n_in_gst_treatment, sub.ecommerce_partner_id, sub.shipping_bill_number, sub.shipping_bill_date, @@ -118,7 +121,7 @@ class L10nInAccountInvoiceReport(models.Model): am.name, am.state, am.date, - am.l10n_in_export_type AS l10n_in_export_type, + am.l10n_in_gst_treatment AS l10n_in_gst_treatment, am.l10n_in_reseller_partner_id AS ecommerce_partner_id, am.l10n_in_shipping_bill_number AS shipping_bill_number, am.l10n_in_shipping_bill_date AS shipping_bill_date, @@ -128,7 +131,7 @@ class L10nInAccountInvoiceReport(models.Model): aj.company_id, am.move_type AS move_type, am.reversed_entry_id AS reversed_entry_id, - p.vat AS partner_vat, + am.l10n_in_gstin AS partner_vat, CASE WHEN rp.vat IS NULL THEN '' ELSE rp.vat END AS ecommerce_vat, (CASE WHEN at.l10n_in_reverse_charge = True THEN True @@ -157,34 +160,32 @@ class L10nInAccountInvoiceReport(models.Model): THEN 'E' ELSE 'OE' END) as b2cs_is_ecommerce, - (CASE WHEN ps.id = cp.state_id or p.id IS NULL + (CASE WHEN am.l10n_in_state_id = cp.state_id or p.id IS NULL THEN 'Intra State' - WHEN ps.id != cp.state_id and p.id IS NOT NULL + WHEN am.l10n_in_state_id != cp.state_id and p.id IS NOT NULL THEN 'Inter State' END) AS supply_type, - (CASE WHEN am.l10n_in_export_type in ('deemed', 'export_with_igst', 'sez_with_igst') + (CASE WHEN am.l10n_in_gst_treatment in ('deemed_export', 'overseas') and am.amount_tax > 0.00 THEN 'EXPWP' - WHEN am.l10n_in_export_type in ('sale_from_bonded_wh', 'sez_without_igst') + WHEN am.l10n_in_gst_treatment in ('deemed_export', 'overseas') and am.amount_tax <= 0.00 THEN 'EXPWOP' ELSE '' END) AS export_type, - (CASE WHEN refund_am.l10n_in_export_type in ('deemed', 'export_with_igst', 'sez_with_igst') + (CASE WHEN am.l10n_in_gst_treatment in ('deemed_export', 'overseas') and am.amount_tax > 0.00 THEN 'EXPWP' - WHEN refund_am.l10n_in_export_type in ('sale_from_bonded_wh', 'sez_without_igst') + WHEN am.l10n_in_gst_treatment in ('deemed_export', 'overseas') and am.amount_tax <= 0.00 THEN 'EXPWOP' ELSE 'B2CL' END) AS refund_export_type, - (CASE WHEN am.l10n_in_export_type = 'regular' + (CASE WHEN am.l10n_in_gst_treatment = 'regular' THEN 'Regular' - WHEN am.l10n_in_export_type = 'deemed' + WHEN am.l10n_in_gst_treatment = 'deemed_export' THEN 'Deemed' - WHEN am.l10n_in_export_type = 'sale_from_bonded_wh' - THEN 'Sale from Bonded WH' - WHEN am.l10n_in_export_type = 'export_with_igst' + WHEN am.l10n_in_gst_treatment = 'overseas' and am.amount_tax > 0.00 THEN 'Export with IGST' - WHEN am.l10n_in_export_type = 'sez_with_igst' + WHEN am.l10n_in_gst_treatment = 'special_economic_zone' and am.amount_tax > 0.00 THEN 'SEZ with IGST payment' - WHEN am.l10n_in_export_type = 'sez_without_igst' + WHEN am.l10n_in_gst_treatment = 'special_economic_zone' and am.amount_tax <= 0.00 THEN 'SEZ without IGST payment' END) AS b2b_type, (CASE WHEN am.move_type = 'out_refund' @@ -250,11 +251,11 @@ class L10nInAccountInvoiceReport(models.Model): JOIN account_account_tag_account_move_line_rel aat_aml_rel ON aat_aml_rel.account_move_line_id = aml.id JOIN account_account_tag aat ON aat.id = aat_aml_rel.account_account_tag_id JOIN account_tax_report_line_tags_rel tag_rep_ln ON aat.id = tag_rep_ln.account_account_tag_id - LEFT JOIN res_partner cp ON cp.id = c.partner_id + LEFT JOIN res_partner cp ON cp.id = COALESCE(aj.l10n_in_gstin_partner_id, c.partner_id) LEFT JOIN res_country_state cps ON cps.id = cp.state_id LEFT JOIN account_move refund_am ON refund_am.id = am.reversed_entry_id LEFT JOIN res_partner p ON p.id = aml.partner_id - LEFT JOIN res_country_state ps ON ps.id = p.state_id + LEFT JOIN res_country_state ps ON ps.id = am.l10n_in_state_id LEFT JOIN res_partner rp ON rp.id = am.l10n_in_reseller_partner_id LEFT JOIN account_tax_filiation_rel atr ON atr.child_tax = at.id LEFT JOIN account_tax parent_at ON parent_at.id = atr.parent_tax @@ -275,7 +276,7 @@ class L10nInAccountInvoiceReport(models.Model): sub.state, sub.partner_id, sub.date, - sub.l10n_in_export_type, + sub.l10n_in_gst_treatment, sub.ecommerce_partner_id, sub.shipping_bill_number, sub.shipping_bill_date, diff --git a/addons/l10n_in/report/account_payment_report.py b/addons/l10n_in/report/account_payment_report.py index d9ae35bbde6..db02965b18d 100644 --- a/addons/l10n_in/report/account_payment_report.py +++ b/addons/l10n_in/report/account_payment_report.py @@ -92,7 +92,7 @@ class L10nInPaymentReport(models.AbstractModel): ELSE c.account_purchase_tax_id END) JOIN res_partner p ON p.id = aml.partner_id LEFT JOIN res_country_state ps ON ps.id = p.state_id - LEFT JOIN res_partner cp ON cp.id = c.partner_id + LEFT JOIN res_partner cp ON cp.id = COALESCE(aj.l10n_in_gstin_partner_id, c.partner_id) LEFT JOIN res_country_state cps ON cps.id = cp.state_id """ diff --git a/addons/l10n_in/report/exempted_gst_report.py b/addons/l10n_in/report/exempted_gst_report.py index 4ea2666935f..661a5c08b9a 100644 --- a/addons/l10n_in/report/exempted_gst_report.py +++ b/addons/l10n_in/report/exempted_gst_report.py @@ -82,7 +82,7 @@ class L10nInExemptedReport(models.Model): JOIN account_account aa ON aa.id = aml.account_id JOIN account_journal aj ON aj.id = am.journal_id JOIN res_company c ON c.id = aj.company_id - LEFT JOIN res_partner cp ON cp.id = c.partner_id + LEFT JOIN res_partner cp ON cp.id = COALESCE(aj.l10n_in_gstin_partner_id, c.partner_id) LEFT JOIN res_partner p ON p.id = am.partner_id LEFT JOIN res_country pc ON pc.id = p.country_id WHERE aa.internal_type = 'other' and aml.tax_line_id IS NULL diff --git a/addons/l10n_in/views/account_invoice_views.xml b/addons/l10n_in/views/account_invoice_views.xml index 5b716a78688..f2f55f341e9 100644 --- a/addons/l10n_in/views/account_invoice_views.xml +++ b/addons/l10n_in/views/account_invoice_views.xml @@ -5,23 +5,27 @@ account.move - - - - - - - + + + + + + + + + + + + + + diff --git a/addons/l10n_in/views/account_journal_views.xml b/addons/l10n_in/views/account_journal_views.xml index 01d68c9733e..626863ae7fb 100644 --- a/addons/l10n_in/views/account_journal_views.xml +++ b/addons/l10n_in/views/account_journal_views.xml @@ -8,9 +8,6 @@ {'invisible': [('type', 'not in', ['bank', 'cash', 'sale', 'purchase'])]} - - - diff --git a/addons/l10n_in/views/report_invoice.xml b/addons/l10n_in/views/report_invoice.xml index 15aa1e270dd..f77aa0134df 100644 --- a/addons/l10n_in/views/report_invoice.xml +++ b/addons/l10n_in/views/report_invoice.xml @@ -2,7 +2,22 @@