def _mx_pac_edi_export_invoice_cfdi(self, invoice): ''' Create the CFDI attachment for the invoice passed as parameter. :param move: An account.move record. :return: A dictionary with one of the following key: * cfdi_str: A string of the unsigned cfdi of the invoice. * error: An error if the cfdi was not successfuly generated. ''' # == CFDI values == cfdi_values = self._mx_pac_edi_get_invoice_cfdi_values(invoice) # == Generate the CFDI == cfdi = self.env.ref('mx_pac_edi.cfdiv33')._render(cfdi_values) decoded_cfdi_values = invoice._mx_pac_edi_decode_cfdi(cfdi_data=cfdi) cfdi_cadena_crypted = cfdi_values['certificate'].sudo().get_encrypted_cadena(decoded_cfdi_values['cadena']) decoded_cfdi_values['cfdi_node'].attrib['Sello'] = cfdi_cadena_crypted # == Optional check using the XSD == xsd_attachment = self.env.ref('mx_pac_edi.xsd_cached_cfdv33_xsd', False) xsd_datas = base64.b64decode(xsd_attachment.datas) if xsd_attachment else None if xsd_datas: try: with BytesIO(xsd_datas) as xsd: _check_with_xsd(decoded_cfdi_values['cfdi_node'], xsd) except (IOError, ValueError): _logger.info(_('The xsd file to validate the XML structure was not found')) except Exception as e: return {'errors': str(e).split('\\n')} return { 'cfdi_str': etree.tostring(decoded_cfdi_values['cfdi_node'], pretty_print=True, xml_declaration=True, encoding='UTF-8'), }
def get_xml(self, options): """ Generate the IRAS Audit File in xml format """ qweb = self.env['ir.qweb'] values = self._get_generic_data(options['date_from'], options['date_to']) doc = qweb.render(IRAS_XML_TEMPLATE, values=values) with tools.file_open(IRAS_XSD, 'rb') as xsd: _check_with_xsd(doc, xsd) tree = fromstring(doc) return etree.tostring(tree, pretty_print=True, xml_declaration=True, encoding='UTF-8')
def get_xml(self, options): qweb = self.env['ir.qweb'] version = '1.3' ctx = self._set_context(options) values = self.with_context(ctx)._get_coa_dict(options) cfdicoa = qweb.render(CFDICOA_TEMPLATE, values=values) for key, value in MX_NS_REFACTORING.items(): cfdicoa = cfdicoa.replace(key.encode('UTF-8'), value.encode('UTF-8') + b':') cfdicoa = self._l10n_mx_edi_add_digital_stamp( CFDICOA_XSLT_CADENA % version, cfdicoa) with tools.file_open(CFDICOA_XSD % version, "rb") as xsd: _check_with_xsd(cfdicoa, xsd) return cfdicoa
def get_xml(self, options): qweb = self.env['ir.qweb'] version = '1.3' ctx = self.set_context(options) if not ctx.get('date_to'): return False ctx['no_format'] = True values = self.with_context(ctx).get_bce_dict(options) cfdicoa = qweb.render(CFDIBCE_TEMPLATE, values=values) for key, value in MX_NS_REFACTORING.items(): cfdicoa = cfdicoa.replace(key.encode('UTF-8'), value.encode('UTF-8') + b':') cfdicoa = self.l10n_mx_edi_add_digital_stamp( CFDIBCE_XSLT_CADENA % version, cfdicoa) with tools.file_open(CFDIBCE_XSD % version, "rb") as xsd: _check_with_xsd(cfdicoa, xsd) return cfdicoa
def l10n_mx_edi_is_cfdi33(self): self.ensure_one() if not self.datas: return False try: datas = base64.b64decode(self.datas).replace( b'xmlns:schemaLocation', b'xsi:schemaLocation') cfdi = objectify.fromstring(datas) except (SyntaxError, ValueError): return False attachment = self.env.ref('l10n_mx_edi.xsd_cached_cfdv33_xsd', False) schema = base64.b64decode(attachment.datas) if attachment else b'' try: if cfdi.get('Version') != '3.3': return False if hasattr(cfdi, 'Addenda'): cfdi.remove(cfdi.Addenda) if not hasattr(cfdi, 'Complemento'): return False if not attachment: return cfdi attribute = 'registrofiscal:CFDIRegistroFiscal' namespace = { 'registrofiscal': 'http://www.sat.gob.mx/registrofiscal' } node = cfdi.Complemento.xpath(attribute, namespaces=namespace) if node: cfdi.Complemento.remove(node[0]) with BytesIO(schema) as xsd: _check_with_xsd(cfdi, xsd) return cfdi except ValueError: return False except IOError: return False except UserError: return False except etree.XMLSyntaxError: return False return False
def get_xml(self, options): qweb = self.env['ir.qweb'] version = '1.3' ctx = self._set_context(options) if not ctx.get('date_to'): return False ctx['no_format'] = True ctx['print_mode'] = True values = self.with_context(ctx).get_bce_dict(options) values.update({ 'request_type': options.get('request_type'), 'order_number': options.get('order_number') or False, 'process_number': options.get('process_number') or False, }) cfdimoves = qweb.render(CFDIPLZ_TEMPLATE, values=values) for key, value in MX_NS_REFACTORING.items(): cfdimoves = cfdimoves.replace(key.encode('UTF-8'), value.encode('UTF-8') + b':') cfdimoves = self.l10n_mx_edi_add_digital_stamp( CFDIPLZ_XSLT % version, cfdimoves) with tools.file_open(CFDIPLZ_XSD % version, "rb") as xsd: _check_with_xsd(cfdimoves, xsd) return cfdimoves
def test_lxml_import_from_filestore(self): resolver_schema_int = b""" <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:etype="http://codespeak.net/lxml/test/external"> <xsd:import namespace="http://codespeak.net/lxml/test/external" schemaLocation="imported_schema.xsd"/> <xsd:element name="a" type="etype:AType"/> </xsd:schema> """ incomplete_schema_int = b""" <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:etype="http://codespeak.net/lxml/test/external"> <xsd:import namespace="http://codespeak.net/lxml/test/external" schemaLocation="non_existing_schema.xsd"/> <xsd:element name="a" type="etype:AType"/> </xsd:schema> """ imported_schema = b""" <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://codespeak.net/lxml/test/external"> <xsd:complexType name="AType"> <xsd:sequence><xsd:element name="b" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/></xsd:sequence> </xsd:complexType> </xsd:schema> """ self.env['ir.attachment'].create([{ 'datas': base64.b64encode(resolver_schema_int), 'name': 'resolver_schema_int.xsd' }, { 'datas': base64.b64encode(incomplete_schema_int), 'name': 'incomplete_schema_int.xsd' }, { 'datas': base64.b64encode(imported_schema), 'name': 'imported_schema.xsd' }]) _check_with_xsd("<a><b></b></a>", 'resolver_schema_int.xsd', self.env) with self.assertRaises(XMLSchemaError): _check_with_xsd("<a><b></b></a>", 'incomplete_schema_int.xsd', self.env) with self.assertRaises(FileNotFoundError): _check_with_xsd("<a><b></b></a>", 'non_existing_schema.xsd', self.env)
def _l10n_mx_edi_create_cfdi(self): """Creates and returns a dictionnary containing 'cfdi' if the cfdi is well created, 'error' otherwise.""" if not self: return {} qweb = self.env['ir.qweb'] invoice_obj = self.env['account.invoice'] company_id = self.mapped('company_id') error_log = [] pac_name = company_id.l10n_mx_edi_pac values = self._l10n_mx_edi_create_cfdi_values() # -Check certificate certificate_ids = company_id.l10n_mx_edi_certificate_ids certificate_id = certificate_ids.sudo().get_valid_certificate() if not certificate_id: error_log.append(_('No valid certificate found')) # -Check PAC if pac_name: pac_test_env = company_id.l10n_mx_edi_pac_test_env pac_password = company_id.l10n_mx_edi_pac_password if not pac_test_env and not pac_password: error_log.append(_('No PAC credentials specified.')) else: error_log.append(_('No PAC specified.')) if error_log: return { 'error': _('Please check your configuration: ') + create_list_html(error_log) } values['certificate_number'] = certificate_id.serial_number values['certificate'] = certificate_id.sudo().get_data()[0] values['date'] = (certificate_id.sudo().get_mx_current_datetime(). strftime('%Y-%m-%dT%H:%M:%S')) cfdi = qweb.render(CFDI_TEMPLATE_33, values=values) attachment = self.env.ref('l10n_mx_edi.xsd_cached_cfdv33_xsd', False) xsd_datas = base64.b64decode(attachment.datas) if attachment else b'' # -Compute cadena tree = objectify.fromstring(cfdi) cadena = invoice_obj.l10n_mx_edi_generate_cadena( CFDI_XSLT_CADENA, tree) tree.attrib['Sello'] = certificate_id.sudo().get_encrypted_cadena( cadena) # Check with xsd if xsd_datas: try: with BytesIO(xsd_datas) as xsd: _check_with_xsd(tree, xsd) except (IOError, ValueError): _logger.info( _('The xsd file to validate the XML structure ' 'was not found')) except BaseException as e: return { 'error': (_('The cfdi generated is not valid') + create_list_html(str(e).split('\\n'))) } return { 'cfdi': etree.tostring(tree, pretty_print=True, xml_declaration=True, encoding='UTF-8') }
def _l10n_mx_edi_create_cfdi(self): '''Creates and returns a dictionnary containing 'cfdi' if the cfdi is well created, 'error' otherwise. ''' self.ensure_one() qweb = self.env['ir.qweb'] error_log = [] company_id = self.company_id pac_name = company_id.l10n_mx_edi_pac values = self._l10n_mx_edi_create_cfdi_values() # ----------------------- # Check the configuration # ----------------------- # -Check certificate certificate_ids = company_id.l10n_mx_edi_certificate_ids certificate_id = certificate_ids.sudo().get_valid_certificate() if not certificate_id: error_log.append(_('No valid certificate found')) # -Check PAC if pac_name: pac_test_env = company_id.l10n_mx_edi_pac_test_env pac_username = company_id.l10n_mx_edi_pac_username pac_password = company_id.l10n_mx_edi_pac_password if not pac_test_env and not (pac_username and pac_password): error_log.append(_('No PAC credentials specified.')) else: error_log.append(_('No PAC specified.')) if error_log: return {'error': _('Please check your configuration: ') + create_list_html(error_log)} # -Compute date and time of the invoice time_invoice = datetime.strptime( self.l10n_mx_edi_time_invoice, DEFAULT_SERVER_TIME_FORMAT).time() # ----------------------- # Create the EDI document # ----------------------- version = self.l10n_mx_edi_get_pac_version() # -Compute certificate data values['date'] = datetime.combine( fields.Datetime.from_string(self.date_invoice), time_invoice).strftime('%Y-%m-%dT%H:%M:%S') values['certificate_number'] = certificate_id.serial_number values['certificate'] = certificate_id.sudo().get_data()[0] # -Compute cfdi if version == '3.2': cfdi = qweb.render(CFDI_TEMPLATE, values=values) node_sello = 'sello' with tools.file_open('l10n_mx_edi/data/%s/cfdi.xsd' % version, 'rb') as xsd: xsd_datas = xsd.read() elif version == '3.3': cfdi = qweb.render(CFDI_TEMPLATE_33, values=values) node_sello = 'Sello' attachment = self.env.ref('l10n_mx_edi.xsd_cached_cfdv33_xsd', False) xsd_datas = base64.b64decode(attachment.datas) if attachment else b'' else: return {'error': _('Unsupported version %s') % version} # -Compute cadena tree = self.l10n_mx_edi_get_xml_etree(cfdi) cadena = self.l10n_mx_edi_generate_cadena(CFDI_XSLT_CADENA % version, tree) tree.attrib[node_sello] = certificate_id.sudo().get_encrypted_cadena(cadena) # Check with xsd if xsd_datas: try: with BytesIO(xsd_datas) as xsd: _check_with_xsd(tree, xsd) except (IOError, ValueError): _logger.info( _('The xsd file to validate the XML structure was not found')) except Exception as e: return {'error': (_('The cfdi generated is not valid') + create_list_html(str(e).split('\\n')))} return {'cfdi': etree.tostring(tree, pretty_print=True, xml_declaration=True, encoding='UTF-8')}
def _l10n_mx_edi_create_cfdi(self): self.ensure_one() if not self.use_addenda: return super(AccountMove, self)._l10n_mx_edi_create_cfdi() else: qweb = self.env['ir.qweb'] error_log = [] company_id = self.company_id pac_name = company_id.l10n_mx_edi_pac if self.l10n_mx_edi_external_trade: # Call the onchange to obtain the values of l10n_mx_edi_qty_umt # and l10n_mx_edi_price_unit_umt, this is necessary when the # invoice is created from the sales order or from the picking self.invoice_line_ids.onchange_quantity() self.invoice_line_ids._set_price_unit_umt() values = self._l10n_mx_edi_create_cfdi_values() # ----------------------- # Check the configuration # ----------------------- # -Check certificate certificate_ids = company_id.l10n_mx_edi_certificate_ids certificate_id = certificate_ids.sudo().get_valid_certificate() if not certificate_id: error_log.append(_('No valid certificate found')) # -Check PAC if pac_name: pac_test_env = company_id.l10n_mx_edi_pac_test_env pac_password = company_id.l10n_mx_edi_pac_password if not pac_test_env and not pac_password: error_log.append(_('No PAC credentials specified.')) else: error_log.append(_('No PAC specified.')) if error_log: return { 'error': _('Please check your configuration: ') + create_list_html(error_log) } # -Compute date and time of the invoice time_invoice = datetime.strptime( self.l10n_mx_edi_time_invoice, DEFAULT_SERVER_TIME_FORMAT).time() # ----------------------- # Create the EDI document # ----------------------- version = self.l10n_mx_edi_get_pac_version() # -Compute certificate data values['date'] = datetime.combine( fields.Datetime.from_string(self.invoice_date), time_invoice).strftime('%Y-%m-%dT%H:%M:%S') values['certificate_number'] = certificate_id.serial_number values['certificate'] = certificate_id.sudo().get_data()[0] # -Compute cfdi cfdi = qweb.render(CFDI_TEMPLATE_33, values=values) cfdi = cfdi.replace(b'xmlns__', b'xmlns:') #Patch for Finkok specifications cfdi_str = cfdi.decode('utf-8') cfdi_str = cfdi_str.replace( 'xmlns:detallista="http://www.sat.gob.mx/detallista"', '') cfdi_str = cfdi_str.replace( '<cfdi:Comprobante', '<cfdi:Comprobante xmlns:detallista="http://www.sat.gob.mx/detallista" ' ) cfdi = cfdi_str.encode('utf-8') #End patch node_sello = 'Sello' attachment = self.env.ref('l10n_mx_edi.xsd_cached_cfdv33_xsd', False) xsd_datas = base64.b64decode( attachment.datas) if attachment else b'' # -Compute cadena tree = self.l10n_mx_edi_get_xml_etree(cfdi) cadena = self.l10n_mx_edi_generate_cadena( CFDI_XSLT_CADENA % version, tree) tree.attrib[node_sello] = certificate_id.sudo( ).get_encrypted_cadena(cadena) # Check with xsd if xsd_datas: try: with BytesIO(xsd_datas) as xsd: _check_with_xsd(tree, xsd) except (IOError, ValueError): _logger.info( _('The xsd file to validate the XML structure was not found' )) except Exception as e: return { 'error': (_('The cfdi generated is not valid') + create_list_html(str(e).split('\\n'))) } return { 'cfdi': etree.tostring(tree, pretty_print=True, xml_declaration=True, encoding='UTF-8') }
def get_xaf(self, options): def cust_sup_tp(partner_id): so_count = partner_id.sale_order_count po_count = partner_id.purchase_order_count if so_count and po_count: return 'B' if so_count: return 'C' if po_count: return 'S' return 'O' def acc_tp(account_id): if account_id.user_type_id.type in ['income', 'expense']: return 'P' if account_id.user_type_id.type in ['asset', 'liability']: return 'B' return 'M' def jrn_tp(journal_id): if journal_id.type == 'bank': return 'B' if journal_id.type == 'cash': return 'C' if journal_id.type == 'situation': return 'O' if journal_id.type in ['sale', 'sale_refund']: return 'S' if journal_id.type in ['purchase', 'purchase_refund']: return 'P' return 'Z' def amnt_tp(move_line_id): return 'C' if move_line_id.credit else 'D' def compute_period_number(date_str): date = fields.Date.from_string(date_str) return date.strftime('%y%m')[1:] def change_date_time(record): return record.write_date.strftime('%Y-%m-%dT%H:%M:%S') company_id = self.env.company msgs = [] if not company_id.vat: msgs.append(_('- VAT number')) if not company_id.country_id: msgs.append(_('- Country')) if msgs: msgs = [_('Some fields must be specified on the company:')] + msgs raise UserError('\n'.join(msgs)) date_from = options['date']['date_from'] date_to = options['date']['date_to'] partner_ids = self.env['res.partner'].search([ '|', ('company_id', '=', False), ('company_id', '=', company_id.id) ]) account_ids = self.env['account.account'].search([('company_id', '=', company_id.id)]) tax_ids = self.env['account.tax'].search([('company_id', '=', company_id.id)]) journal_ids = self.env['account.journal'].search([('company_id', '=', company_id.id)]) # Retrieve periods values periods = [] Period = namedtuple('Period', 'number name date_from date_to') for period in rrule(freq=MONTHLY, bymonth=(), dtstart=fields.Date.from_string(date_from), until=fields.Date.from_string(date_to)): period_from = fields.Date.to_string(period.date()) period_to = period.replace( day=calendar.monthrange(period.year, period.month)[1]) period_to = fields.Date.to_string(period_to.date()) periods.append( Period(number=compute_period_number(period_from), name=period.strftime('%B') + ' ' + date_from[0:4], date_from=period_from, date_to=period_to)) # Retrieve move lines values total_query = """ SELECT COUNT(*), SUM(l.debit), SUM(l.credit) FROM account_move_line l, account_move m WHERE l.move_id = m.id AND l.date >= %s AND l.date <= %s AND l.company_id = %s AND m.state != 'draft' """ self.env.cr.execute(total_query, ( date_from, date_to, company_id.id, )) moves_count, moves_debit, moves_credit = self.env.cr.fetchall()[0] journal_x_moves = {} for journal in journal_ids: journal_x_moves[journal] = self.env['account.move'].search([ ('date', '>=', date_from), ('date', '<=', date_to), ('state', '!=', 'draft'), ('journal_id', '=', journal.id) ]) values = { 'company_id': company_id, 'partner_ids': partner_ids, 'account_ids': account_ids, 'journal_ids': journal_ids, 'journal_x_moves': journal_x_moves, 'compute_period_number': compute_period_number, 'periods': periods, 'tax_ids': tax_ids, 'cust_sup_tp': cust_sup_tp, 'acc_tp': acc_tp, 'jrn_tp': jrn_tp, 'amnt_tp': amnt_tp, 'change_date_time': change_date_time, 'fiscal_year': date_from[0:4], 'date_from': date_from, 'date_to': date_to, 'date_created': fields.Date.context_today(self), 'software_version': release.version, 'moves_count': moves_count, 'moves_debit': moves_debit or 0.0, 'moves_credit': moves_credit or 0.0, } audit_content = self.env['ir.qweb'].render( 'l10n_nl_reports.xaf_audit_file', values) with tools.file_open('l10n_nl_reports/data/xml_audit_file_3_2.xsd', 'rb') as xsd: _check_with_xsd(audit_content, xsd) return audit_content