Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xsdata and --compound-field #40

Open
rvalyi opened this issue Mar 24, 2022 · 2 comments
Open

xsdata and --compound-field #40

rvalyi opened this issue Mar 24, 2022 · 2 comments

Comments

@rvalyi
Copy link
Member

rvalyi commented Mar 24, 2022

Na nova branch master-xsdata que usa xsdata, temos um pequeno problema com o elemento IPI da tag Imposto. Da forma como é o xsd, ou precisa de um patch de 1 linha no xsdata, ou precisa usar a opçâo --compound-field do xsdata. Eu detalhei o problema aqui: tefra/xsdata#666

De inicio, pode parecer uma boa usar a opção --compound-field já que resolve o problema no binding gerido como aqui. Porem tem um outro lado da moeda: quando usar essa opção, tem pelo menos 2 campos (IPI do Imposto e um outro no Transp) que viram então um campo composto com uma hierarquia mais funda:

Sem a opção --compound-field e com meu patch tefra/xsdata#666:

            @dataclass
            class Imposto:

                v_tot_trib: Optional[str] = field(
                    default=None,
                    metadata={
                        "name": "vTotTrib",
                        "type": "Element",
                        "namespace": "http://www.portalfiscal.inf.br/nfe",
                        "white_space": "preserve",
                        "pattern": r"0|0\.[0-9]{2}|[1-9]{1}[0-9]{0,12}(\.[0-9]{2})?",
                    }
                )
                icms: Optional["Tnfe.InfNfe.Det.Imposto.Icms"] = field(
                    default=None,
                    metadata={
                        "name": "ICMS",
                        "type": "Element",
                        "namespace": "http://www.portalfiscal.inf.br/nfe",
                    }
                )
                ipi: Optional[Tipi] = field(
                    default=None,
                    metadata={
                        "name": "IPI",
                        "type": "Element",
                        "namespace": "http://www.portalfiscal.inf.br/nfe",
                    }
                )

Agora com a opção --compound-field e sem meu patch:

            @dataclass
            class Imposto:
                v_tot_trib: Optional[str] = field(
                    default=None,
                    metadata={
                        "name": "vTotTrib",
                        "type": "Element",
                        "namespace": "http://www.portalfiscal.inf.br/nfe",
                        "white_space": "preserve",
                        "pattern": r"0|0\.[0-9]{2}|[1-9]{1}[0-9]{0,12}(\.[0-9]{2})?",
                    }   
                )  
                icms: Optional["Tnfe.InfNfe.Det.Imposto.Icms"] = field(
                    default=None,
                    metadata={
                        "name": "ICMS",
                        "type": "Element",
                        "namespace": "http://www.portalfiscal.inf.br/nfe",
                    }
                )  
                choice: List[object] = field(
                    default_factory=list,
                    metadata={
                        "type": "Elements",
                        "choices": (
                            {
                                "name": "IPI",
                                "type": Tipi,
                                "namespace": "http://www.portalfiscal.inf.br/nfe",
                            },
                            {
                                "name": "II",
                                "type": Type["Tnfe.InfNfe.Det.Imposto.Ii"],
                                "namespace": "http://www.portalfiscal.inf.br/nfe",
                            },
                            {
                                "name": "ISSQN",
                                "type": Type["Tnfe.InfNfe.Det.Imposto.Issqn"],
                                "namespace": "http://www.portalfiscal.inf.br/nfe",
                            },
                            {
                                "name": "PIS",
                                "type": Type["Tnfe.InfNfe.Det.Imposto.Pis"],
                                "namespace": "http://www.portalfiscal.inf.br/nfe",
                            },
                            {
                                "name": "PISST",
                                "type": Type["Tnfe.InfNfe.Det.Imposto.Pisst"],
                                "namespace": "http://www.portalfiscal.inf.br/nfe",
                            },
                            {
                                "name": "COFINS",
                                "type": Type["Tnfe.InfNfe.Det.Imposto.Cofins"],
                                "namespace": "http://www.portalfiscal.inf.br/nfe",
                            },
                            {
                                "name": "COFINSST",
                                "type": Type["Tnfe.InfNfe.Det.Imposto.Cofinsst"],
                                "namespace": "http://www.portalfiscal.inf.br/nfe",
                            },
                            {
                                "name": "ICMSUFDest",
                                "type": Type["Tnfe.InfNfe.Det.Imposto.Icmsufdest"],
                                "namespace": "http://www.portalfiscal.inf.br/nfe",
                            },
                        ),
                        "max_occurs": 9,
                    }
                )

Nesse caso o xsdata tem uma espece de gestão de choice.

O grande ponto é que nos temos um interesso não apenas nos bindings Python mas tb nos modelos Odoo geridos pelo plugin https://github.com/akretion/xsdata-odoo
Esses modelos Odoo são de persistência e é interessante que seja muito mais "planos" do que os modelos super fundos do XSD que servem apenas para validação XSD. E usar a opção --compound-field iria atrapalhar nisso porque nos obrigaria a repensar o mapeamento entre um campo Odoo e um campo de um binding dessa lib, criaria mais complexidade no mapping.
Se essa opção tb lidava com as verdadeiras tags choice do xsd como fazia o generateDS (que nos interessa apenas nas visões automáticas, ou seja muito pouco hoje), eu bancaria essa complexidade. Mas não é o caso, seria uma complexidade meio inútil.

Eu fiz uma busca exaustiva e o problema acontece apenas com essa tag IPI na dezena de esquemas de NFe que temos nessa branch. Com tudo o meu fix aqui tefra/xsdata#666 resolve isso perfeitamente (eu gerei tudo de novo e vi que o impacto era apenas o esperado aqui).

Nisso minha escolha é de usar esse patch de uma linha no xsdata ou então no código Python gerido apenas pela tag IPI.

cc @mbcosta @renatonlima @marcelsavegnago @netosjb @felipemotter

rvalyi added a commit that referenced this issue Mar 24, 2022
@rvalyi
Copy link
Member Author

rvalyi commented Aug 9, 2022

O fix ainda é necessario, mas no xsdata o nome do arquivo mudou. Eu refiz o commit xsdata aqui então:
akretion/xsdata@933ddc0

@rvalyi rvalyi pinned this issue Aug 9, 2022
@rvalyi
Copy link
Member Author

rvalyi commented Apr 23, 2023

atualização: o método process foi ré-escrito na futura versão do xsdata: tefra/xsdata@4dc9b41

agora tem que aplicar um patch no metodo: merge_duplicate_attrs

@classmethod
def merge_duplicate_attrs(self, target: Class):
    result: List[Attr] = []
    for attr in target.attrs:
        pos = collections.find(result, attr)
        existing = result[pos] if pos > -1 else None

        if not existing:
            result.append(attr)
        elif not (attr.is_attribute or attr.is_enumeration):
            existing.help = existing.help or attr.help

            e_res = existing.restrictions
            a_res = attr.restrictions

            min_occurs = e_res.min_occurs or 0
            max_occurs = e_res.max_occurs or 1
            attr_min_occurs = a_res.min_occurs or 0
            attr_max_occurs = a_res.max_occurs or 1

            e_res.min_occurs = min(min_occurs, attr_min_occurs)
            e_res.max_occurs = min(max_occurs, attr_max_occurs)  # this is the patch

            if a_res.sequence is not None:
                e_res.sequence = a_res.sequence

            existing.fixed = False
            existing.types.extend(attr.types)

    target.attrs = result
    ClassUtils.cleanup_class(target)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant