parent
6a4677c3a6
commit
d374361a82
|
@ -56,6 +56,7 @@ class TaxTemplate(metaclass=PoolMeta):
|
|||
|
||||
return res
|
||||
|
||||
|
||||
class Tax(metaclass=PoolMeta):
|
||||
__name__ = 'account.tax'
|
||||
|
||||
|
|
234
invoice.py
234
invoice.py
|
@ -1,17 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# The COPYRIGHT file at the top level of this repository contains the full
|
||||
# copyright notices and license terms.
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import base64
|
||||
import random
|
||||
import xmlsig
|
||||
import hashlib
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from lxml import etree
|
||||
from operator import attrgetter
|
||||
from subprocess import Popen, PIPE
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.serialization import pkcs12
|
||||
from trytond.model import ModelView, fields
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Bool, Eval
|
||||
|
@ -151,6 +155,16 @@ class Invoice(metaclass=PoolMeta):
|
|||
}
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def copy(cls, invoices, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
else:
|
||||
default = default.copy()
|
||||
default.setdefault('invoice_facturae', None)
|
||||
default.setdefault('rectificative_reason_code', None)
|
||||
return super(Invoice, cls).copy(invoices, default=default)
|
||||
|
||||
def get_credited_invoices(self, name):
|
||||
pool = Pool()
|
||||
InvoiceLine = pool.get('account.invoice.line')
|
||||
|
@ -308,7 +322,7 @@ class Invoice(metaclass=PoolMeta):
|
|||
'account_invoice_facturae.invoice_address_fields',
|
||||
invoice=self.rec_name))
|
||||
|
||||
euro, = Currency.search([('code', '=', 'EUR')])
|
||||
euro, = Currency.search([('code', '=', 'EUR')], limit=1)
|
||||
if self.currency != euro:
|
||||
assert (euro.rate == Decimal(1)
|
||||
or self.currency.rate == Decimal(1)), (
|
||||
|
@ -436,52 +450,184 @@ class Invoice(metaclass=PoolMeta):
|
|||
cert_file.write(self.company.facturae_certificate)
|
||||
cert_file.close()
|
||||
|
||||
signed_file = NamedTemporaryFile(suffix='.xsig', delete=False)
|
||||
def _sign_file(cert, password, request):
|
||||
(
|
||||
private_key,
|
||||
certificate,
|
||||
additional_certificates,
|
||||
) = pkcs12.load_key_and_certificates(cert, password)
|
||||
# key = private_key.private_bytes(
|
||||
# encoding=serialization.Encoding.PEM,
|
||||
# format=serialization.PrivateFormat.PKCS8,
|
||||
# encryption_algorithm=serialization.NoEncryption(),
|
||||
# )
|
||||
# DER is an ASN.1 encoding type
|
||||
crt = certificate.public_bytes(serialization.Encoding.DER)
|
||||
|
||||
env = {}
|
||||
env.update(os.environ)
|
||||
libs = os.path.join(module_path(), 'java', 'lib', '*.jar')
|
||||
env['CLASSPATH'] = ':'.join(glob.glob(libs))
|
||||
rand_min = 1
|
||||
rand_max = 99999
|
||||
signature_id = "Signature%05d" % random.randint(rand_min, rand_max)
|
||||
signed_properties_id = (
|
||||
signature_id
|
||||
+ "-SignedProperties%05d" % random.randint(rand_min, rand_max)
|
||||
)
|
||||
key_info_id = "KeyInfo%05d" % random.randint(rand_min, rand_max)
|
||||
reference_id = "Reference%05d" % random.randint(rand_min, rand_max)
|
||||
object_id = "Object%05d" % random.randint(rand_min, rand_max)
|
||||
etsi = "http://uri.etsi.org/01903/v1.3.2#"
|
||||
sig_policy_identifier = (
|
||||
"http://www.facturae.es/"
|
||||
"politica_de_firma_formato_facturae/"
|
||||
"politica_de_firma_formato_facturae_v3_1"
|
||||
".pdf"
|
||||
)
|
||||
sig_policy_hash_value = "Ohixl6upD6av8N7pEvDABhEL6hM="
|
||||
root = etree.fromstring(request)
|
||||
sign = xmlsig.template.create(
|
||||
c14n_method=xmlsig.constants.TransformInclC14N,
|
||||
sign_method=xmlsig.constants.TransformRsaSha1,
|
||||
name=signature_id,
|
||||
ns="ds",
|
||||
)
|
||||
key_info = xmlsig.template.ensure_key_info(sign, name=key_info_id)
|
||||
x509_data = xmlsig.template.add_x509_data(key_info)
|
||||
xmlsig.template.x509_data_add_certificate(x509_data)
|
||||
xmlsig.template.add_key_value(key_info)
|
||||
|
||||
# TODO: implement Signer with python
|
||||
# http://www.pyopenssl.org/en/stable/api/crypto.html#OpenSSL.crypto.load_pkcs12
|
||||
signature_command = [
|
||||
'java',
|
||||
'-Djava.awt.headless=true',
|
||||
'com.nantic.facturae.Signer',
|
||||
'0',
|
||||
unsigned_file.name,
|
||||
signed_file.name,
|
||||
'facturae31',
|
||||
cert_file.name,
|
||||
certificate_password
|
||||
]
|
||||
signature_process = Popen(signature_command,
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
env=env,
|
||||
cwd=os.path.join(module_path(), 'java'))
|
||||
output, err = signature_process.communicate()
|
||||
rc = signature_process.returncode
|
||||
if rc != 0:
|
||||
logger.warning('Error %s signing invoice "%s" with command '
|
||||
'"%s <password>": %s %s', rc, self.id,
|
||||
signature_command[:-1], output, err)
|
||||
raise UserError(gettext(
|
||||
'account_invoice_factura.error_signing',
|
||||
invoice=self.rec_name,
|
||||
process_output=output))
|
||||
xmlsig.template.add_reference(
|
||||
sign,
|
||||
xmlsig.constants.TransformSha1,
|
||||
uri="#" + signed_properties_id,
|
||||
uri_type="http://uri.etsi.org/01903#SignedProperties",
|
||||
)
|
||||
xmlsig.template.add_reference(
|
||||
sign, xmlsig.constants.TransformSha1, uri="#" + key_info_id
|
||||
)
|
||||
ref = xmlsig.template.add_reference(
|
||||
sign, xmlsig.constants.TransformSha1, name=reference_id, uri=""
|
||||
)
|
||||
xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped)
|
||||
object_node = etree.SubElement(
|
||||
sign,
|
||||
etree.QName(xmlsig.constants.DSigNs, "Object"),
|
||||
nsmap={"etsi": etsi},
|
||||
attrib={xmlsig.constants.ID_ATTR: object_id},
|
||||
)
|
||||
qualifying_properties = etree.SubElement(
|
||||
object_node,
|
||||
etree.QName(etsi, "QualifyingProperties"),
|
||||
attrib={"Target": "#" + signature_id},
|
||||
)
|
||||
signed_properties = etree.SubElement(
|
||||
qualifying_properties,
|
||||
etree.QName(etsi, "SignedProperties"),
|
||||
attrib={xmlsig.constants.ID_ATTR: signed_properties_id},
|
||||
)
|
||||
signed_signature_properties = etree.SubElement(
|
||||
signed_properties, etree.QName(etsi, "SignedSignatureProperties")
|
||||
)
|
||||
now = datetime.datetime.now()
|
||||
etree.SubElement(
|
||||
signed_signature_properties, etree.QName(etsi, "SigningTime")
|
||||
).text = now.isoformat()
|
||||
signing_certificate = etree.SubElement(
|
||||
signed_signature_properties, etree.QName(etsi, "SigningCertificate")
|
||||
)
|
||||
signing_certificate_cert = etree.SubElement(
|
||||
signing_certificate, etree.QName(etsi, "Cert")
|
||||
)
|
||||
cert_digest = etree.SubElement(
|
||||
signing_certificate_cert, etree.QName(etsi, "CertDigest")
|
||||
)
|
||||
etree.SubElement(
|
||||
cert_digest,
|
||||
etree.QName(xmlsig.constants.DSigNs, "DigestMethod"),
|
||||
attrib={"Algorithm": "http://www.w3.org/2000/09/xmldsig#sha1"},
|
||||
)
|
||||
|
||||
hash_cert = hashlib.sha1(crt)
|
||||
etree.SubElement(
|
||||
cert_digest, etree.QName(xmlsig.constants.DSigNs, "DigestValue")
|
||||
).text = base64.b64encode(hash_cert.digest())
|
||||
|
||||
issuer_serial = etree.SubElement(
|
||||
signing_certificate_cert, etree.QName(etsi, "IssuerSerial")
|
||||
)
|
||||
etree.SubElement(
|
||||
issuer_serial, etree.QName(xmlsig.constants.DSigNs, "X509IssuerName")
|
||||
).text = xmlsig.utils.get_rdns_name(certificate.issuer.rdns)
|
||||
etree.SubElement(
|
||||
issuer_serial, etree.QName(xmlsig.constants.DSigNs, "X509SerialNumber")
|
||||
).text = str(certificate.serial_number)
|
||||
|
||||
signature_policy_identifier = etree.SubElement(
|
||||
signed_signature_properties,
|
||||
etree.QName(etsi, "SignaturePolicyIdentifier"),
|
||||
)
|
||||
signature_policy_id = etree.SubElement(
|
||||
signature_policy_identifier, etree.QName(etsi, "SignaturePolicyId")
|
||||
)
|
||||
sig_policy_id = etree.SubElement(
|
||||
signature_policy_id, etree.QName(etsi, "SigPolicyId")
|
||||
)
|
||||
etree.SubElement(
|
||||
sig_policy_id, etree.QName(etsi, "Identifier")
|
||||
).text = sig_policy_identifier
|
||||
etree.SubElement(
|
||||
sig_policy_id, etree.QName(etsi, "Description")
|
||||
).text = "Política de Firma FacturaE v3.1"
|
||||
sig_policy_hash = etree.SubElement(
|
||||
signature_policy_id, etree.QName(etsi, "SigPolicyHash")
|
||||
)
|
||||
etree.SubElement(
|
||||
sig_policy_hash,
|
||||
etree.QName(xmlsig.constants.DSigNs, "DigestMethod"),
|
||||
attrib={"Algorithm": "http://www.w3.org/2000/09/xmldsig#sha1"},
|
||||
)
|
||||
hash_value = sig_policy_hash_value
|
||||
etree.SubElement(
|
||||
sig_policy_hash, etree.QName(xmlsig.constants.DSigNs, "DigestValue")
|
||||
).text = hash_value
|
||||
signer_role = etree.SubElement(
|
||||
signed_signature_properties, etree.QName(etsi, "SignerRole")
|
||||
)
|
||||
claimed_roles = etree.SubElement(
|
||||
signer_role, etree.QName(etsi, "ClaimedRoles")
|
||||
)
|
||||
etree.SubElement(
|
||||
claimed_roles, etree.QName(etsi, "ClaimedRole")
|
||||
).text = "supplier"
|
||||
signed_data_object_properties = etree.SubElement(
|
||||
signed_properties, etree.QName(etsi, "SignedDataObjectProperties")
|
||||
)
|
||||
data_object_format = etree.SubElement(
|
||||
signed_data_object_properties,
|
||||
etree.QName(etsi, "DataObjectFormat"),
|
||||
attrib={"ObjectReference": "#" + reference_id},
|
||||
)
|
||||
etree.SubElement(
|
||||
data_object_format, etree.QName(etsi, "Description")
|
||||
).text = "Factura"
|
||||
etree.SubElement(
|
||||
data_object_format, etree.QName(etsi, "MimeType")
|
||||
).text = "text/xml"
|
||||
ctx = xmlsig.SignatureContext()
|
||||
ctx.x509 = certificate
|
||||
ctx.public_key = certificate.public_key()
|
||||
ctx.private_key = private_key
|
||||
root.append(sign)
|
||||
ctx.sign(sign)
|
||||
return etree.tostring(root, xml_declaration=True, encoding="UTF-8")
|
||||
|
||||
signed_file_content = _sign_file(
|
||||
self.company.facturae_certificate,
|
||||
certificate_password.encode(),
|
||||
xml_string,
|
||||
)
|
||||
|
||||
logger.info("Factura-e for invoice %s (%s) generated and signed",
|
||||
self.rec_name, self.id)
|
||||
|
||||
signed_file_content = signed_file.read()
|
||||
signed_file.close()
|
||||
|
||||
os.unlink(unsigned_file.name)
|
||||
os.unlink(cert_file.name)
|
||||
os.unlink(signed_file.name)
|
||||
|
||||
return signed_file_content
|
||||
|
||||
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
package com.nantic.facturae;
|
||||
|
||||
import com.nantic.facturae.xmlsign.Sign;
|
||||
|
||||
public class Signer {
|
||||
|
||||
public static void main (String [] args) {
|
||||
if (args.length < 1) {
|
||||
System.out.println("Missing operation param");
|
||||
System.exit(-1);
|
||||
}
|
||||
int op = Integer.parseInt(args[0]);
|
||||
switch (op) {
|
||||
case 0: {
|
||||
if (args.length < 6) {
|
||||
System.out.println("Missing expected params: 0 " +
|
||||
"<file-to-sign.xml> <target-file.xsig> <policy> " +
|
||||
"<PKCS12-certificate.p12> <password>");
|
||||
System.exit(-1);
|
||||
}
|
||||
Sign s = new Sign();
|
||||
s.FirmaXADES_EPES(args[1], args[2], args[3], args[4], args[5]);
|
||||
break;
|
||||
}
|
||||
// case 1: {
|
||||
// Verify v = new Verify();
|
||||
// if (v.validarFichero(args[1])) break;
|
||||
// System.out.println("La firma no es valida.");
|
||||
// System.exit(2001);
|
||||
// break;
|
||||
// }
|
||||
// case 2: {
|
||||
// PDFSign pdfsign = new PDFSign();
|
||||
// float[] dim = new float[]{Float.parseFloat(args[8]), Float.parseFloat(args[9]), Float.parseFloat(args[10]), Float.parseFloat(args[11])};
|
||||
// try {
|
||||
// pdfsign.FirmaPDF(args[1], args[2], args[3], args[4], args[5], args[6], args[7], dim[0], dim[1], dim[2], dim[3]);
|
||||
// }
|
||||
// catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// System.exit(3001);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
default: {
|
||||
System.out.println("Unexpected operation");
|
||||
System.exit(9001);
|
||||
}
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.nantic.facturae.xmlsign;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import es.mityc.javasign.pkstore.IPassStoreKS;
|
||||
|
||||
public class PassStoreKS implements IPassStoreKS {
|
||||
private transient String password;
|
||||
|
||||
public PassStoreKS(String pass) {
|
||||
this.password = new String(pass);
|
||||
}
|
||||
|
||||
public char[] getPassword(X509Certificate certificate, String alias) {
|
||||
return this.password.toCharArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
package com.nantic.facturae.xmlsign;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.URI;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import es.mityc.firmaJava.libreria.utilidades.UtilidadTratarNodo;
|
||||
import es.mityc.firmaJava.libreria.xades.DataToSign;
|
||||
import es.mityc.javasign.EnumFormatoFirma;
|
||||
import es.mityc.firmaJava.libreria.xades.FirmaXML;
|
||||
import es.mityc.firmaJava.libreria.xades.XAdESSchemas;
|
||||
import es.mityc.firmaJava.libreria.xades.elementos.xades.ObjectIdentifier;
|
||||
import es.mityc.javasign.pkstore.CertStoreException;
|
||||
import es.mityc.javasign.pkstore.IPassStoreKS;
|
||||
import es.mityc.javasign.pkstore.keystore.KSStore;
|
||||
import es.mityc.javasign.xml.refs.AbstractObjectToSign;
|
||||
import es.mityc.javasign.xml.refs.AllXMLToSign;
|
||||
import es.mityc.javasign.xml.refs.ObjectToSign;
|
||||
|
||||
import com.nantic.facturae.xmlsign.PassStoreKS;
|
||||
|
||||
public class Sign extends FirmaXML {
|
||||
public void FirmaXADES_EPES(String xmlOrigen, String xmlDestino, String policy, String certificado, String password) {
|
||||
KSStore storeManager = null;
|
||||
try {
|
||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||
ks.load(new FileInputStream(certificado), password.toCharArray());
|
||||
storeManager = new KSStore(ks, (IPassStoreKS)new PassStoreKS(password));
|
||||
}
|
||||
catch (KeyStoreException ex) {
|
||||
System.out.println("Error KeyStoreException...");
|
||||
System.exit(1001);
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println("Error NoSuchAlgorithmException...");
|
||||
System.exit(1002);
|
||||
}
|
||||
catch (CertificateException ex) {
|
||||
System.out.println("Error CertificateException...");
|
||||
System.exit(1003);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
System.out.println("Error IOException... Provably the supplied password is incorrect");
|
||||
System.exit(1004);
|
||||
}
|
||||
|
||||
List certs = null;
|
||||
try {
|
||||
certs = storeManager.getSignCertificates();
|
||||
}
|
||||
catch (CertStoreException ex) {
|
||||
System.out.println("Error al obtener los certificados");
|
||||
System.exit(1005);
|
||||
}
|
||||
if (certs == null || certs.size() == 0) {
|
||||
System.out.println("No hay certificados");
|
||||
System.exit(1006);
|
||||
}
|
||||
X509Certificate certificate = (X509Certificate)certs.get(0);
|
||||
PrivateKey privateKey = null;
|
||||
try {
|
||||
privateKey = storeManager.getPrivateKey(certificate);
|
||||
}
|
||||
catch (CertStoreException e) {
|
||||
System.out.println("Error al acceder al almac\u00e9n");
|
||||
System.exit(1007);
|
||||
}
|
||||
Provider provider = storeManager.getProvider(certificate);
|
||||
DataToSign dataToSign = this.createDataToSign(xmlOrigen, policy);
|
||||
Object[] res = null;
|
||||
try {
|
||||
res = this.signFile(certificate, dataToSign, privateKey, provider);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
System.out.println("Error!!!");
|
||||
System.exit(1008);
|
||||
}
|
||||
try {
|
||||
UtilidadTratarNodo.saveDocumentToOutputStream((Document)((Document)res[0]), (OutputStream)new FileOutputStream(xmlDestino), (boolean)true);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
System.out.println("Error!");
|
||||
System.exit(1009);
|
||||
}
|
||||
System.out.println("\u00a1XML de origen firmado correctamente!.");
|
||||
}
|
||||
|
||||
private DataToSign createDataToSign(String xmlOrigen, String policy) {
|
||||
DataToSign dataToSign = new DataToSign();
|
||||
dataToSign.setXadesFormat(EnumFormatoFirma.XAdES_BES);
|
||||
dataToSign.setEsquema(XAdESSchemas.XAdES_132);
|
||||
dataToSign.setPolicyKey(policy);
|
||||
dataToSign.setAddPolicy(true);
|
||||
dataToSign.setXMLEncoding("UTF-8");
|
||||
dataToSign.setEnveloped(true);
|
||||
dataToSign.addObject(new ObjectToSign((AbstractObjectToSign)new AllXMLToSign(), "Documento de ejemplo", null, "text/xml", null));
|
||||
Document docToSign = this.getDocument(xmlOrigen);
|
||||
dataToSign.setDocument(docToSign);
|
||||
return dataToSign;
|
||||
}
|
||||
|
||||
private Document getDocument(String resource) {
|
||||
Document doc = null;
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setNamespaceAware(true);
|
||||
try {
|
||||
doc = dbf.newDocumentBuilder().parse(new BufferedInputStream(new FileInputStream(resource)));
|
||||
}
|
||||
catch (ParserConfigurationException ex) {
|
||||
System.err.println("Error al parsear el documento");
|
||||
ex.printStackTrace();
|
||||
System.exit(-1);
|
||||
}
|
||||
catch (SAXException ex) {
|
||||
System.err.println("Error al parsear el documento");
|
||||
ex.printStackTrace();
|
||||
System.exit(-1);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
System.err.println("Error al parsear el documento");
|
||||
ex.printStackTrace();
|
||||
System.exit(-1);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
System.err.println("Error al parsear el documento");
|
||||
ex.printStackTrace();
|
||||
System.exit(-1);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
GENERATED_JAR="nantic-facturae-0.1.jar"
|
||||
|
||||
rm lib/$GENERATED_JAR
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
directories="/usr/lib/jvm/java-7-openjdk-amd64/bin /usr/lib/j2sdk1.6-sun /usr/lib/j2sdk1.5-sun"
|
||||
for d in $directories; do
|
||||
if [ -d "$d" ]; then
|
||||
export JAVA_HOME="$d"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# echo "JAVA_HOME=$JAVA_HOME"
|
||||
# export PATH="$JAVA_HOME"/bin:/bin:/usr/bin
|
||||
export CLASSPATH=$(ls -1 lib/* | grep jar$ | awk '{printf "%s:", $1}')
|
||||
|
||||
# echo "Class-Path: $(ls -1 lib/* | grep jar$ | awk '{printf "%s:", $1}')" > Manifest.txt
|
||||
|
||||
FILES=$(find com -iname "*.java")
|
||||
|
||||
echo "Compiling com.nantic.facturae"
|
||||
javac -Xlint:deprecation $FILES || exit
|
||||
|
||||
# echo "Main-Class: com.nantic.facturae.Signer" > Manifest.txt
|
||||
# echo "Class-Path:" >> Manifest.txt
|
||||
# for jarfile in `ls -1 lib/*.jar`
|
||||
# do
|
||||
# echo " $jarfile" >> Manifest.txt
|
||||
# done
|
||||
# echo "" >> Manifest.txt
|
||||
#
|
||||
# jar cvfm nantic-facturae-0.1.jar Manifest.txt com
|
||||
jar cvf $GENERATED_JAR com
|
||||
mv $GENERATED_JAR lib/$GENERATED_JAR
|
||||
|
||||
# echo "Executing java com.nantic.facturae.Sign"
|
||||
export CLASSPATH="lib/$GENERATED_JAR"":$CLASSPATH"
|
||||
java com.nantic.facturae.Signer
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -10,21 +10,21 @@
|
|||
<BatchIdentifier>{{ ('%s%s' % (invoice.company.party.tax_identifier.code, invoice.number))[:70] }}</BatchIdentifier>
|
||||
<InvoicesCount>1</InvoicesCount>
|
||||
<TotalInvoicesAmount>
|
||||
<TotalAmount>{{ invoice.total_amount }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
</TotalInvoicesAmount>
|
||||
<TotalOutstandingAmount>
|
||||
{# TODO: it must to get amount_to_pay? #}
|
||||
<TotalAmount>{{ invoice.total_amount }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
</TotalOutstandingAmount>
|
||||
<TotalExecutableAmount>
|
||||
{# TODO: it must to get amount_to_pay? #}
|
||||
<TotalAmount>{{ invoice.total_amount }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
|
@ -161,7 +161,7 @@
|
|||
<InvoiceCurrencyCode>{{ invoice.currency.code.upper() }}</InvoiceCurrencyCode>
|
||||
{% if invoice.currency != euro %}
|
||||
<ExchangeRateDetails>
|
||||
<ExchangeRate>{{ exchange_rate }}</ExchangeRate>
|
||||
<ExchangeRate>{{ Currency.compute(invoice.currency, exchange_rate, euro) }}</ExchangeRate>
|
||||
<ExchangeRateDate>{{ exchange_rate_date }}</ExchangeRateDate>
|
||||
</ExchangeRateDetails>
|
||||
{% endif %}
|
||||
|
@ -172,15 +172,15 @@
|
|||
{% for invoice_tax in invoice.taxes_outputs %}
|
||||
<Tax>
|
||||
<TaxTypeCode>{{ invoice_tax.tax.report_type }}</TaxTypeCode>
|
||||
<TaxRate>{{ invoice_tax.tax.rate * 100 }}</TaxRate>
|
||||
<TaxRate>{{ Currency.compute(invoice.currency, invoice_tax.tax.rate * 100, euro) }}</TaxRate>
|
||||
<TaxableBase>
|
||||
<TotalAmount>{{ invoice_tax.base }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, invoice_tax.base, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice_tax.base, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
</TaxableBase>
|
||||
<TaxAmount>
|
||||
<TotalAmount>{{ invoice_tax.amount }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, invoice_tax.amount, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice_tax.amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
|
@ -193,7 +193,7 @@
|
|||
{# TODO: EquivalenceSurchace must to have its own Tax entry or it must to go to the IVA line? TaxRate == EquivalenceSurcharge and TaxAmount == EquivalenceSurchargeAmount? #}
|
||||
<EquivalenceSurcharge>{{ (invoice_tax.tax.rate * 100).quantize(Decimal('0.01')) }}</EquivalenceSurcharge>
|
||||
<EquivalenceSurchargeAmount>
|
||||
<TotalAmount>{{ invoice_tax.amount }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, invoice_tax.amount, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice_tax.amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
|
@ -207,15 +207,15 @@
|
|||
{% for invoice_tax in invoice.taxes_withheld %}
|
||||
<Tax>
|
||||
<TaxTypeCode>{{ invoice_tax.tax.report_type }}</TaxTypeCode>
|
||||
<TaxRate>{{ invoice_tax.tax.rate * 100 }}</TaxRate>
|
||||
<TaxRate>{{ Currency.compute(invoice.currency, invoice_tax.tax.rate * 100, euro) }}</TaxRate>
|
||||
<TaxableBase>
|
||||
<TotalAmount>{{ invoice_tax.base }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, invoice_tax.base, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice_tax.base, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
</TaxableBase>
|
||||
<TaxAmount>
|
||||
<TotalAmount>{{ invoice_tax.amount }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, invoice_tax.amount, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice_tax.amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
|
@ -225,13 +225,13 @@
|
|||
</TaxesWithheld>
|
||||
{% endif %}
|
||||
<InvoiceTotals>
|
||||
<TotalGrossAmount>{{ invoice.untaxed_amount }}</TotalGrossAmount>
|
||||
<TotalGrossAmount>{{ Currency.compute(invoice.currency, invoice.untaxed_amount, euro) }}</TotalGrossAmount>
|
||||
{# TODO: GeneralDiscounts and TotalGeneralDiscounts (account_invoice_discount_global) not supported #}
|
||||
{# TODO: GeneralSurcharges and TotalGeneralSurcharges not supported #}
|
||||
<TotalGrossAmountBeforeTaxes>{{ invoice.untaxed_amount }}</TotalGrossAmountBeforeTaxes>
|
||||
<TotalTaxOutputs>{{ invoice.taxes_outputs | sum(attribute='amount', start=Decimal(0)) }}</TotalTaxOutputs>
|
||||
<TotalTaxesWithheld>{{ invoice.taxes_withheld | sum(attribute='amount', start=Decimal(0)) }}</TotalTaxesWithheld>
|
||||
<InvoiceTotal>{{ invoice.total_amount }}</InvoiceTotal>
|
||||
<TotalGrossAmountBeforeTaxes>{{ Currency.compute(invoice.currency, invoice.untaxed_amount, euro) }}</TotalGrossAmountBeforeTaxes>
|
||||
<TotalTaxOutputs>{{ Currency.compute(invoice.currency, invoice.taxes_outputs | sum(attribute='amount', start=Decimal(0)), euro) }}</TotalTaxOutputs>
|
||||
<TotalTaxesWithheld>{{ Currency.compute(invoice.currency, invoice.taxes_withheld | sum(attribute='amount', start=Decimal(0)), euro) }}</TotalTaxesWithheld>
|
||||
<InvoiceTotal>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</InvoiceTotal>
|
||||
{# TODO: optional, not supported
|
||||
- Subsidies
|
||||
- PaymentsOnAccount, TotalPaymentsOnAccount
|
||||
|
@ -239,8 +239,8 @@
|
|||
- TotalFinancialExpenses (account_payment_type_cost?)
|
||||
- AmountsWithheld
|
||||
#}
|
||||
<TotalOutstandingAmount>{{ invoice.total_amount }}</TotalOutstandingAmount>
|
||||
<TotalExecutableAmount>{{ invoice.total_amount }}</TotalExecutableAmount>
|
||||
<TotalOutstandingAmount>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</TotalOutstandingAmount>
|
||||
<TotalExecutableAmount>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</TotalExecutableAmount>
|
||||
</InvoiceTotals>
|
||||
<Items>
|
||||
{% for line in invoice.lines if line.type == 'line' %}
|
||||
|
@ -258,27 +258,27 @@
|
|||
<ItemDescription>{{ line.description and line.description[:2500] or '' }}</ItemDescription>
|
||||
<Quantity>{{ line.quantity }}</Quantity>
|
||||
<UnitOfMeasure>{{ UOM_CODE2TYPE.get(line.unit.symbol, '05') if line.unit else '05' }}</UnitOfMeasure>
|
||||
<UnitPriceWithoutTax>{{ line.unit_price }}</UnitPriceWithoutTax>
|
||||
<TotalCost>{{ line.amount }}</TotalCost>
|
||||
<UnitPriceWithoutTax>{{ Currency.compute(invoice.currency, line.unit_price, euro) }}</UnitPriceWithoutTax>
|
||||
<TotalCost>{{ Currency.compute(invoice.currency, line.amount, euro) }}</TotalCost>
|
||||
{# TODO: optional, not supported
|
||||
- DiscountsAndRebates (account_invoice_discount)
|
||||
- Charges
|
||||
#}
|
||||
<GrossAmount>{{ line.amount }}</GrossAmount>
|
||||
<GrossAmount>{{ Currency.compute(invoice.currency, line.amount, euro) }}</GrossAmount>
|
||||
{% if line.taxes_withheld %}
|
||||
<TaxesWithheld>
|
||||
{% for line_tax in invoice.taxes_withheld %}
|
||||
<Tax>
|
||||
<TaxTypeCode>{{ line_tax.tax.report_type }}</TaxTypeCode>
|
||||
<TaxRate>{{ line_tax.tax.rate * 100 }}</TaxRate>
|
||||
<TaxRate>{{ Currency.compute(invoice.currency, line_tax.tax.rate * 100, euro) }}</TaxRate>
|
||||
<TaxableBase>
|
||||
<TotalAmount>{{ line.amount }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, line.amount, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, line.amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
</TaxableBase>
|
||||
<TaxAmount>
|
||||
<TotalAmount>{{ line.amount * line_tax.tax.rate }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, line.amount * line_tax.tax.rate, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, line.amount * line_tax.tax.rate, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
|
@ -291,15 +291,15 @@
|
|||
{% for line_tax in line.taxes_outputs %}
|
||||
<Tax>
|
||||
<TaxTypeCode>{{ line_tax.tax.report_type }}</TaxTypeCode>
|
||||
<TaxRate>{{ line_tax.tax.rate * 100 }}</TaxRate>
|
||||
<TaxRate>{{ Currency.compute(invoice.currency, line_tax.tax.rate * 100, euro) }}</TaxRate>
|
||||
<TaxableBase>
|
||||
<TotalAmount>{{ line.amount }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, line.amount, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, line.amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
</TaxableBase>
|
||||
<TaxAmount>
|
||||
<TotalAmount>{{ line.amount * line_tax.tax.rate }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, line.amount * line_tax.tax.rate, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, line.amount * line_tax.tax.rate, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
|
@ -312,7 +312,7 @@
|
|||
{# TODO: EquivalenceSurchace must to have its own Tax entry or it must to go to the IVA line? TaxRate == EquivalenceSurcharge and TaxAmount == EquivalenceSurchargeAmount? #}
|
||||
<EquivalenceSurcharge>{{ (line_tax.tax.rate * 100).quantize(Decimal('0.01')) }}</EquivalenceSurcharge>
|
||||
<EquivalenceSurchargeAmount>
|
||||
<TotalAmount>{{ line.amount * line_tax.tax.rate }}</TotalAmount>
|
||||
<TotalAmount>{{ Currency.compute(invoice.currency, line.amount * line_tax.tax.rate, euro) }}</TotalAmount>
|
||||
{% if invoice.currency != euro %}
|
||||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, line.amount * line_tax.tax.rate, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
|
@ -340,7 +340,7 @@
|
|||
{% for move_line in invoice.payment_details %}
|
||||
<Installment>
|
||||
<InstallmentDueDate>{{ move_line.maturity_date.isoformat() }}</InstallmentDueDate>
|
||||
<InstallmentAmount>{{ ((move_line.debit - move_line.credit) | abs).quantize(Decimal('0.01')) }}</InstallmentAmount>
|
||||
<InstallmentAmount>{{ Currency.compute(invoice.currency, ((move_line.debit - move_line.credit) | abs).quantize(Decimal('0.01')), euro) }}</InstallmentAmount>
|
||||
<PaymentMeans>{{ move_line.payment_type.facturae_type }}</PaymentMeans>
|
||||
{% if move_line.payment_type.facturae_type == '04' %}
|
||||
<AccountToBeCredited>
|
||||
|
@ -376,7 +376,7 @@
|
|||
- RelatedDocuments
|
||||
- Extensions
|
||||
#}
|
||||
<InvoiceAdditionalInformation>Factura generada con Tryton (http://www.tryton.org)</InvoiceAdditionalInformation>
|
||||
<InvoiceAdditionalInformation>Factura generada con Tryton (https://www.tryton.org)</InvoiceAdditionalInformation>
|
||||
</AdditionalData>
|
||||
</Invoice>
|
||||
</Invoices>
|
||||
|
|
|
@ -67,30 +67,6 @@ class TestAccountInvoiceFacturaeCase(ModuleTestCase):
|
|||
CURRENT_PATH, 'certificate.pfx'), 'rb') as cert_file:
|
||||
company.facturae_certificate = cert_file.read()
|
||||
|
||||
payment_term, = PaymentTerm.create([{
|
||||
'name': '20 days, 40 days',
|
||||
'lines': [
|
||||
('create', [{
|
||||
'sequence': 0,
|
||||
'type': 'percent',
|
||||
'divisor': 2,
|
||||
'ratio': Decimal('.5'),
|
||||
'relativedeltas': [('create', [{
|
||||
'days': 20,
|
||||
},
|
||||
]),
|
||||
],
|
||||
}, {
|
||||
'sequence': 1,
|
||||
'type': 'remainder',
|
||||
'relativedeltas': [('create', [{
|
||||
'days': 40,
|
||||
},
|
||||
]),
|
||||
],
|
||||
}])]
|
||||
}])
|
||||
|
||||
with set_company(company):
|
||||
create_chart(company, tax=True)
|
||||
|
||||
|
@ -225,7 +201,8 @@ class TestAccountInvoiceFacturaeCase(ModuleTestCase):
|
|||
Invoice.post([invoice])
|
||||
|
||||
Invoice.generate_facturae_default([invoice], 'privatepassword')
|
||||
|
||||
self.assertNotEqual(invoice.invoice_facturae, None)
|
||||
self.assertEqual(invoice.invoice_facturae_filename, 'facturae-1.xsig')
|
||||
|
||||
def suite():
|
||||
suite = trytond.tests.test_tryton.suite()
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<!--The COPYRIGHT file at the top level of this repository
|
||||
contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="/form/notebook/page[@id='general']/field[@name='update_unit_price']"
|
||||
position="after">
|
||||
<newline/>
|
||||
<xpath expr="/form/notebook/page[@name='legal_notice']" position="after">
|
||||
<page string="Factura-e" id="factura-e">
|
||||
<label name="report_type"/>
|
||||
<field name="report_type"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
</data>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<!--The COPYRIGHT file at the top level of this repository
|
||||
contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="/form/notebook/page[@id='general']/field[@name='update_unit_price']"
|
||||
position="after">
|
||||
<newline/>
|
||||
<xpath expr="/form/notebook/page[@name='legal_notice']" position="after">
|
||||
<page string="Factura-e" id="factura-e">
|
||||
<label name="report_type"/>
|
||||
<field name="report_type"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
</data>
|
||||
|
|
Loading…
Reference in New Issue