@@ -25,6 +25,9 @@ public function buildDps(DpsData $dps): string
2525 $ doc ->preserveWhiteSpace = false ;
2626 $ doc ->formatOutput = true ;
2727
28+ $ emissionDateTime = $ this ->formattedEmissionDateTime ();
29+ $ competenceDate = $ dps ->dataCompetencia ?: substr ($ emissionDateTime , 0 , 10 );
30+
2831 $ root = $ doc ->createElementNS (self ::XSD_NAMESPACE , 'DPS ' );
2932 $ root ->setAttribute ('versao ' , self ::DPS_VERSION );
3033 $ root ->setAttribute ('xsi:schemaLocation ' , self ::XSD_SCHEMA );
@@ -35,42 +38,27 @@ public function buildDps(DpsData $dps): string
3538 $ infDps ->setAttribute ('Id ' , $ this ->buildIdentifier ($ dps ));
3639 $ root ->appendChild ($ infDps );
3740
38- // Municipality
39- $ cMun = $ doc ->createElement ('cMun ' , $ dps ->municipioIbge );
40- $ infDps ->appendChild ($ cMun );
41+ $ infDps ->appendChild ($ doc ->createElement ('tpAmb ' , (string ) $ dps ->tipoAmbiente ));
42+ $ infDps ->appendChild ($ doc ->createElement ('dhEmi ' , $ emissionDateTime ));
43+ $ infDps ->appendChild ($ doc ->createElement ('verAplic ' , $ dps ->versaoAplicativo ));
44+ $ infDps ->appendChild ($ doc ->createElement ('serie ' , str_pad ($ dps ->serie , 5 , '0 ' , STR_PAD_LEFT )));
45+ $ infDps ->appendChild ($ doc ->createElement ('nDPS ' , $ dps ->numeroDps ));
46+ $ infDps ->appendChild ($ doc ->createElement ('dCompet ' , $ competenceDate ));
47+ $ infDps ->appendChild ($ doc ->createElement ('tpEmit ' , (string ) $ dps ->tipoEmissao ));
4148 $ infDps ->appendChild ($ doc ->createElement ('cLocEmi ' , $ dps ->municipioIbge ));
4249
43- // Prestador
4450 $ prest = $ doc ->createElement ('prest ' );
4551 $ cnpj = $ doc ->createElement ('CNPJ ' , $ dps ->cnpjPrestador );
4652 $ prest ->appendChild ($ cnpj );
4753 $ prest ->appendChild ($ this ->buildRegTrib ($ doc , $ dps ));
4854 $ infDps ->appendChild ($ prest );
4955
50- // Service block
51- $ serv = $ doc ->createElement ('serv ' );
52-
53- $ itemListaServico = $ doc ->createElement ('cServ ' );
54- $ itemListaServico ->appendChild ($ doc ->createElement ('cTribNac ' , $ dps ->codigoTributacaoNacional ));
55- $ serv ->appendChild ($ itemListaServico );
56-
57- $ serv ->appendChild ($ doc ->createElement ('xDescServ ' , htmlspecialchars ($ dps ->discriminacao , ENT_XML1 )));
58- $ infDps ->appendChild ($ serv );
59-
60- // Tomador (optional — absent for foreign buyers with no document)
6156 if ($ dps ->documentoTomador !== '' ) {
6257 $ infDps ->appendChild ($ this ->buildToma ($ doc , $ dps ));
6358 }
6459
65- // Values
66- $ valores = $ doc ->createElement ('valores ' );
67-
68- $ vServPrest = $ doc ->createElement ('vServPrest ' );
69- $ vServPrest ->appendChild ($ doc ->createElement ('vServ ' , $ dps ->valorServico ));
70- $ valores ->appendChild ($ vServPrest );
71-
72- $ valores ->appendChild ($ this ->buildTotTrib ($ doc , $ dps ));
73- $ infDps ->appendChild ($ valores );
60+ $ infDps ->appendChild ($ this ->buildServico ($ doc , $ dps ));
61+ $ infDps ->appendChild ($ this ->buildValores ($ doc , $ dps ));
7462
7563 return $ doc ->saveXML () ?: '' ;
7664 }
@@ -85,35 +73,90 @@ private function buildIdentifier(DpsData $dps): string
8573 . str_pad ($ dps ->numeroDps , 15 , '0 ' , STR_PAD_LEFT );
8674 }
8775
88- private function buildTotTrib (\DOMDocument $ doc , DpsData $ dps ): \DOMElement
76+ private function buildValores (\DOMDocument $ doc , DpsData $ dps ): \DOMElement
8977 {
90- $ totTrib = $ doc ->createElement ('totTrib ' );
78+ $ valores = $ doc ->createElement ('valores ' );
9179
92- // tribMun contains ISS and conditional pAliq
80+ $ vServPrest = $ doc ->createElement ('vServPrest ' );
81+ $ vServPrest ->appendChild ($ doc ->createElement ('vServ ' , $ dps ->valorServico ));
82+ $ valores ->appendChild ($ vServPrest );
83+
84+ $ trib = $ doc ->createElement ('trib ' );
85+ $ trib ->appendChild ($ this ->buildTribMun ($ doc , $ dps ));
86+
87+ if ($ this ->hasFederalTaxationData ($ dps )) {
88+ $ trib ->appendChild ($ this ->buildTribFederal ($ doc , $ dps ));
89+ }
90+
91+ if ($ this ->hasTotalTributosPercentuais ($ dps )) {
92+ $ trib ->appendChild ($ this ->buildTotTrib ($ doc , $ dps ));
93+ }
94+
95+ $ valores ->appendChild ($ trib );
96+
97+ return $ valores ;
98+ }
99+
100+ private function buildTribMun (\DOMDocument $ doc , DpsData $ dps ): \DOMElement
101+ {
93102 $ tribMun = $ doc ->createElement ('tribMun ' );
94103 $ tribMun ->appendChild ($ doc ->createElement ('tribISSQN ' , $ dps ->issRetido ? '2 ' : '1 ' ));
95104 $ tribMun ->appendChild ($ doc ->createElement ('tpRetISSQN ' , (string ) $ dps ->tipoRetencaoIss ));
96105
97- // E0617: For não optante (opSimpNac=1), pAliq must NOT be present
98106 if ($ dps ->opcaoSimplesNacional !== 1 ) {
99107 $ tribMun ->appendChild ($ doc ->createElement ('pAliq ' , $ dps ->aliquota ));
100108 }
101109
102- $ totTrib ->appendChild ($ tribMun );
110+ return $ tribMun ;
111+ }
103112
104- if ($ this ->hasFederalTaxationData ($ dps )) {
105- $ totTrib ->appendChild ($ this ->buildTribFederal ($ doc , $ dps ));
113+ private function buildTotTrib (\DOMDocument $ doc , DpsData $ dps ): \DOMElement
114+ {
115+ $ totTrib = $ doc ->createElement ('totTrib ' );
116+ $ percentuais = $ doc ->createElement ('pTotTrib ' );
117+
118+ if ($ dps ->totalTributosPercentualFederal !== '' ) {
119+ $ percentuais ->appendChild ($ doc ->createElement ('pTotTribFed ' , $ dps ->totalTributosPercentualFederal ));
120+ }
121+
122+ if ($ dps ->totalTributosPercentualEstadual !== '' ) {
123+ $ percentuais ->appendChild ($ doc ->createElement ('pTotTribEst ' , $ dps ->totalTributosPercentualEstadual ));
124+ }
125+
126+ if ($ dps ->totalTributosPercentualMunicipal !== '' ) {
127+ $ percentuais ->appendChild ($ doc ->createElement ('pTotTribMun ' , $ dps ->totalTributosPercentualMunicipal ));
106128 }
107129
108- // E0715: indTotTrib is ALWAYS included to avoid schema validation errors
109- $ totTrib ->appendChild ($ doc ->createElement ('indTotTrib ' , (string ) $ dps ->indicadorTributacao ));
130+ $ totTrib ->appendChild ($ percentuais );
110131
111132 return $ totTrib ;
112133 }
113134
135+ private function buildServico (\DOMDocument $ doc , DpsData $ dps ): \DOMElement
136+ {
137+ $ serv = $ doc ->createElement ('serv ' );
138+
139+ $ locPrest = $ doc ->createElement ('locPrest ' );
140+ $ locPrest ->appendChild ($ doc ->createElement ('cLocPrestacao ' , $ dps ->municipioIbge ));
141+ $ serv ->appendChild ($ locPrest );
142+
143+ $ cServ = $ doc ->createElement ('cServ ' );
144+ $ cServ ->appendChild ($ doc ->createElement ('cTribNac ' , $ dps ->codigoTributacaoNacional ));
145+
146+ if ($ dps ->itemListaServico !== '' ) {
147+ $ cServ ->appendChild ($ doc ->createElement ('cTribMun ' , $ dps ->itemListaServico ));
148+ }
149+
150+ $ cServ ->appendChild ($ doc ->createElement ('xDescServ ' , htmlspecialchars ($ dps ->discriminacao , ENT_XML1 )));
151+ $ serv ->appendChild ($ cServ );
152+
153+ return $ serv ;
154+ }
155+
114156 private function buildRegTrib (\DOMDocument $ doc , DpsData $ dps ): \DOMElement
115157 {
116158 $ regTrib = $ doc ->createElement ('regTrib ' );
159+ $ regTrib ->appendChild ($ doc ->createElement ('opSimpNac ' , (string ) $ dps ->opcaoSimplesNacional ));
117160 $ regTrib ->appendChild ($ doc ->createElement ('regEspTrib ' , (string ) $ dps ->regimeEspecialTributacao ));
118161
119162 return $ regTrib ;
@@ -141,33 +184,57 @@ private function buildToma(\DOMDocument $doc, DpsData $dps): \DOMElement
141184 private function buildTribFederal (\DOMDocument $ doc , DpsData $ dps ): \DOMElement
142185 {
143186 $ tribFed = $ doc ->createElement ('tribFed ' );
187+ $ piscofins = $ doc ->createElement ('piscofins ' );
144188
145189 if ($ dps ->federalPiscofinsSituacaoTributaria !== '' ) {
146- $ tribFed ->appendChild ($ doc ->createElement ('sitTribPISCOFINS ' , $ dps ->federalPiscofinsSituacaoTributaria ));
190+ $ piscofins ->appendChild ($ doc ->createElement ('CST ' , str_pad ($ dps ->federalPiscofinsSituacaoTributaria , 2 , '0 ' , STR_PAD_LEFT )));
191+ }
192+
193+ foreach ([
194+ 'vBCPisCofins ' => $ dps ->federalPiscofinsBaseCalculo ,
195+ 'pAliqPis ' => $ dps ->federalPiscofinsAliquotaPis ,
196+ 'pAliqCofins ' => $ dps ->federalPiscofinsAliquotaCofins ,
197+ 'vPis ' => $ dps ->federalPiscofinsValorPis ,
198+ 'vCofins ' => $ dps ->federalPiscofinsValorCofins ,
199+ ] as $ tag => $ value ) {
200+ if ($ value !== '' ) {
201+ $ piscofins ->appendChild ($ doc ->createElement ($ tag , $ value ));
202+ }
147203 }
148204
149205 if ($ dps ->federalPiscofinsTipoRetencao !== '' ) {
150- $ tribFed ->appendChild ($ doc ->createElement ('tpRetPISCOFINSCSLL ' , $ dps ->federalPiscofinsTipoRetencao ));
206+ $ piscofins ->appendChild ($ doc ->createElement ('tpRetPisCofins ' , $ dps ->federalPiscofinsTipoRetencao ));
207+ }
208+
209+ if ($ piscofins ->hasChildNodes ()) {
210+ $ tribFed ->appendChild ($ piscofins );
151211 }
152212
153213 foreach ([
154- 'vBCPISCOFINS ' => $ dps ->federalPiscofinsBaseCalculo ,
155- 'pAliqPIS ' => $ dps ->federalPiscofinsAliquotaPis ,
156- 'vPIS ' => $ dps ->federalPiscofinsValorPis ,
157- 'pAliqCOFINS ' => $ dps ->federalPiscofinsAliquotaCofins ,
158- 'vCOFINS ' => $ dps ->federalPiscofinsValorCofins ,
159- 'vIRRF ' => $ dps ->federalValorIrrf ,
160- 'vCSLL ' => $ dps ->federalValorCsll ,
161- 'vCP ' => $ dps ->federalValorCp ,
214+ 'vRetIRRF ' => $ dps ->federalValorIrrf ,
215+ 'vRetCSLL ' => $ dps ->federalValorCsll ,
216+ 'vRetCP ' => $ dps ->federalValorCp ,
162217 ] as $ tag => $ value ) {
163- if ($ value !== '' ) {
218+ if ($ this -> hasNonZeroDecimalValue ( $ value) ) {
164219 $ tribFed ->appendChild ($ doc ->createElement ($ tag , $ value ));
165220 }
166221 }
167222
168223 return $ tribFed ;
169224 }
170225
226+ private function formattedEmissionDateTime (): string
227+ {
228+ return (new \DateTimeImmutable ())->format ('Y-m-d \\TH:i:sP ' );
229+ }
230+
231+ private function hasTotalTributosPercentuais (DpsData $ dps ): bool
232+ {
233+ return $ dps ->totalTributosPercentualFederal !== ''
234+ || $ dps ->totalTributosPercentualEstadual !== ''
235+ || $ dps ->totalTributosPercentualMunicipal !== '' ;
236+ }
237+
171238 private function hasFederalTaxationData (DpsData $ dps ): bool
172239 {
173240 return $ dps ->federalPiscofinsSituacaoTributaria !== ''
@@ -177,8 +244,13 @@ private function hasFederalTaxationData(DpsData $dps): bool
177244 || $ dps ->federalPiscofinsValorPis !== ''
178245 || $ dps ->federalPiscofinsAliquotaCofins !== ''
179246 || $ dps ->federalPiscofinsValorCofins !== ''
180- || $ dps ->federalValorIrrf !== ''
181- || $ dps ->federalValorCsll !== ''
182- || $ dps ->federalValorCp !== '' ;
247+ || $ this ->hasNonZeroDecimalValue ($ dps ->federalValorIrrf )
248+ || $ this ->hasNonZeroDecimalValue ($ dps ->federalValorCsll )
249+ || $ this ->hasNonZeroDecimalValue ($ dps ->federalValorCp );
250+ }
251+
252+ private function hasNonZeroDecimalValue (string $ value ): bool
253+ {
254+ return $ value !== '' && (float ) $ value !== 0.0 ;
183255 }
184256}
0 commit comments