@@ -72,14 +72,46 @@ public function testBuildDpsContainsMunicipioIbge(): void
7272
7373 self ::assertStringContainsString ('3303302 ' , $ xml );
7474 self ::assertStringContainsString ('<cLocEmi>3303302</cLocEmi> ' , $ xml );
75+ self ::assertStringContainsString ('<cLocPrestacao>3303302</cLocPrestacao> ' , $ xml );
76+ }
77+
78+ public function testBuildDpsStartsInfDpsWithTpAmbBeforeMunicipalityFields (): void
79+ {
80+ $ xml = $ this ->builder ->buildDps ($ this ->makeDps (municipioIbge: '3303302 ' ));
81+
82+ $ doc = new \DOMDocument ();
83+ $ doc ->loadXML ($ xml );
84+ $ xpath = new \DOMXPath ($ doc );
85+ $ xpath ->registerNamespace ('n ' , 'http://www.sped.fazenda.gov.br/nfse ' );
86+
87+ $ infDps = $ xpath ->query ('//n:infDPS ' )->item (0 );
88+ self ::assertNotNull ($ infDps );
89+
90+ $ headerElements = [];
91+
92+ foreach ($ infDps ?->childNodes ?? [] as $ childNode ) {
93+ if ($ childNode instanceof \DOMElement) {
94+ $ headerElements [] = $ childNode ->localName ;
95+ }
96+ }
97+
98+ self ::assertSame (
99+ ['tpAmb ' , 'dhEmi ' , 'verAplic ' , 'serie ' , 'nDPS ' , 'dCompet ' , 'tpEmit ' , 'cLocEmi ' ],
100+ array_slice ($ headerElements , 0 , 8 ),
101+ );
102+ self ::assertSame ('2 ' , $ xpath ->query ('//n:infDPS/n:tpAmb ' )->item (0 )?->textContent);
103+ self ::assertSame (0 , $ xpath ->query ('//n:infDPS/n:cMun ' )->length );
75104 }
76105
77106 public function testBuildDpsUsesNationalTaxCodeInCtribnac (): void
78107 {
79108 $ dps = $ this ->makeDps (itemListaServico: '0107 ' , codigoTributacaoNacional: '101011 ' );
80109 $ xml = $ this ->builder ->buildDps ($ dps );
81110
82- self ::assertStringContainsString ('<cServ><cTribNac>101011</cTribNac></cServ> ' , str_replace (["\n" , ' ' ], '' , $ xml ));
111+ self ::assertStringContainsString (
112+ '<cServ><cTribNac>101011</cTribNac><cTribMun>0107</cTribMun><xDescServ>Consultoria em TI</xDescServ></cServ> ' ,
113+ str_replace (["\n" , ' ' ], '' , $ xml ),
114+ );
83115 }
84116
85117 public function testBuildDpsContainsValorServico (): void
@@ -193,6 +225,10 @@ public function testBuildDpsAlwaysIncludesProviderTaxRegime(): void
193225 $ nodes = $ xpath ->query ('//n:prest/n:regTrib/n:regEspTrib ' );
194226 self ::assertSame (1 , $ nodes ->length , '<prest><regTrib><regEspTrib> must always be present ' );
195227 self ::assertSame ('0 ' , $ nodes ->item (0 )->textContent );
228+
229+ $ simplesNodes = $ xpath ->query ('//n:prest/n:regTrib/n:opSimpNac ' );
230+ self ::assertSame (1 , $ simplesNodes ->length , '<prest><regTrib><opSimpNac> must always be present ' );
231+ self ::assertSame ('1 ' , $ simplesNodes ->item (0 )->textContent );
196232 }
197233
198234 public function testBuildDpsIncludesFederalTaxationBlockWhenConfigured (): void
@@ -213,16 +249,37 @@ public function testBuildDpsIncludesFederalTaxationBlockWhenConfigured(): void
213249 $ xml = $ this ->builder ->buildDps ($ dps );
214250
215251 self ::assertStringContainsString ('<tribFed> ' , $ xml );
216- self ::assertStringContainsString ('<sitTribPISCOFINS>1</sitTribPISCOFINS> ' , $ xml );
217- self ::assertStringContainsString ('<tpRetPISCOFINSCSLL>3</tpRetPISCOFINSCSLL> ' , $ xml );
218- self ::assertStringContainsString ('<vBCPISCOFINS>1000.00</vBCPISCOFINS> ' , $ xml );
219- self ::assertStringContainsString ('<pAliqPIS>1.65</pAliqPIS> ' , $ xml );
220- self ::assertStringContainsString ('<vPIS>16.50</vPIS> ' , $ xml );
221- self ::assertStringContainsString ('<pAliqCOFINS>7.60</pAliqCOFINS> ' , $ xml );
222- self ::assertStringContainsString ('<vCOFINS>76.00</vCOFINS> ' , $ xml );
223- self ::assertStringContainsString ('<vIRRF>15.00</vIRRF> ' , $ xml );
224- self ::assertStringContainsString ('<vCSLL>10.00</vCSLL> ' , $ xml );
225- self ::assertStringContainsString ('<vCP>5.00</vCP> ' , $ xml );
252+ self ::assertStringContainsString ('<piscofins> ' , $ xml );
253+ self ::assertStringContainsString ('<CST>01</CST> ' , $ xml );
254+ self ::assertStringContainsString ('<tpRetPisCofins>3</tpRetPisCofins> ' , $ xml );
255+ self ::assertStringContainsString ('<vBCPisCofins>1000.00</vBCPisCofins> ' , $ xml );
256+ self ::assertStringContainsString ('<pAliqPis>1.65</pAliqPis> ' , $ xml );
257+ self ::assertStringContainsString ('<vPis>16.50</vPis> ' , $ xml );
258+ self ::assertStringContainsString ('<pAliqCofins>7.60</pAliqCofins> ' , $ xml );
259+ self ::assertStringContainsString ('<vCofins>76.00</vCofins> ' , $ xml );
260+ self ::assertStringContainsString ('<vRetIRRF>15.00</vRetIRRF> ' , $ xml );
261+ self ::assertStringContainsString ('<vRetCSLL>10.00</vRetCSLL> ' , $ xml );
262+ self ::assertStringContainsString ('<vRetCP>5.00</vRetCP> ' , $ xml );
263+ }
264+
265+ public function testBuildDpsOmitsZeroValuedOptionalFederalRetentions (): void
266+ {
267+ $ xml = $ this ->builder ->buildDps ($ this ->makeDps (
268+ federalPiscofinsSituacaoTributaria: '1 ' ,
269+ federalPiscofinsTipoRetencao: '4 ' ,
270+ federalPiscofinsBaseCalculo: '14227.50 ' ,
271+ federalPiscofinsAliquotaPis: '0.65 ' ,
272+ federalPiscofinsValorPis: '92.48 ' ,
273+ federalPiscofinsAliquotaCofins: '3.00 ' ,
274+ federalPiscofinsValorCofins: '426.83 ' ,
275+ federalValorIrrf: '472.50 ' ,
276+ federalValorCsll: '0.00 ' ,
277+ federalValorCp: '0.00 ' ,
278+ ));
279+
280+ self ::assertStringContainsString ('<vRetIRRF>472.50</vRetIRRF> ' , $ xml );
281+ self ::assertStringNotContainsString ('<vRetCSLL> ' , $ xml );
282+ self ::assertStringNotContainsString ('<vRetCP> ' , $ xml );
226283 }
227284
228285 // -------------------------------------------------------------------------
@@ -240,15 +297,10 @@ public function testNonSimplesnacionalMustNotIncludeIndtottribAndPaliq(): void
240297 );
241298 $ xml = $ this ->builder ->buildDps ($ dpsNaoOptante );
242299
243- // For "não optante" (opSimpNac = 1), pAliq must NOT be present
244- // but indTotTrib is now ALWAYS included (even if 0) to avoid schema validation errors
300+ // For "não optante" (opSimpNac = 1), pAliq must NOT be present.
245301 self ::assertStringNotContainsString ('<pAliq> ' , $ xml );
246- self ::assertStringContainsString ('<indTotTrib> ' , $ xml );
247-
248- // totTrib container must exist with content
249- self ::assertStringContainsString ('<totTrib ' , $ xml );
302+ self ::assertStringNotContainsString ('<pTotTrib> ' , $ xml );
250303
251- // tribMun and tribISSQN must still exist
252304 self ::assertStringContainsString ('<tribMun> ' , $ xml );
253305 self ::assertStringContainsString ('<tribISSQN> ' , $ xml );
254306 }
@@ -263,13 +315,24 @@ public function testOptiontSimplesnacionalIncludesIndtottribAndPaliq(): void
263315 valorServico: '1000.00 ' ,
264316 aliquota: '5.00 ' ,
265317 opcaoSimplesNacional: 2 , // optante
266- indicadorTributacao: 1 ,
267318 );
268319 $ xml = $ this ->builder ->buildDps ($ dpsOptante );
269320
270- // For "optante" (opSimpNac = 2), indTotTrib and pAliq MUST be present
271- self ::assertStringContainsString ('<indTotTrib> ' , $ xml );
321+ // For "optante" (opSimpNac = 2), pAliq MUST be present.
272322 self ::assertStringContainsString ('<pAliq> ' , $ xml );
323+ self ::assertStringNotContainsString ('<pTotTrib> ' , $ xml );
324+ }
325+
326+ public function testBuildDpsIncludesTotalTributosPercentuaisWhenConfigured (): void
327+ {
328+ $ xml = $ this ->builder ->buildDps ($ this ->makeDps (
329+ indicadorTributacao: 2 ,
330+ totalTributosPercentualFederal: '3.65 ' ,
331+ totalTributosPercentualEstadual: '0.00 ' ,
332+ totalTributosPercentualMunicipal: '2.00 ' ,
333+ ));
334+
335+ self ::assertStringContainsString ('<pTotTrib><pTotTribFed>3.65</pTotTribFed><pTotTribEst>0.00</pTotTribEst><pTotTribMun>2.00</pTotTribMun></pTotTrib> ' , str_replace (["\n" , ' ' ], '' , $ xml ));
273336 }
274337
275338 // -------------------------------------------------------------------------
@@ -291,6 +354,9 @@ private function makeDps(
291354 int $ tipoRetencaoIss = 1 ,
292355 int $ opcaoSimplesNacional = 1 ,
293356 int $ indicadorTributacao = 0 ,
357+ string $ totalTributosPercentualFederal = '' ,
358+ string $ totalTributosPercentualEstadual = '' ,
359+ string $ totalTributosPercentualMunicipal = '' ,
294360 string $ federalPiscofinsSituacaoTributaria = '' ,
295361 string $ federalPiscofinsTipoRetencao = '' ,
296362 string $ federalPiscofinsBaseCalculo = '' ,
@@ -319,6 +385,9 @@ private function makeDps(
319385 issRetido: $ issRetido ,
320386 opcaoSimplesNacional: $ opcaoSimplesNacional ,
321387 indicadorTributacao: $ indicadorTributacao ,
388+ totalTributosPercentualFederal: $ totalTributosPercentualFederal ,
389+ totalTributosPercentualEstadual: $ totalTributosPercentualEstadual ,
390+ totalTributosPercentualMunicipal: $ totalTributosPercentualMunicipal ,
322391 federalPiscofinsSituacaoTributaria: $ federalPiscofinsSituacaoTributaria ,
323392 federalPiscofinsTipoRetencao: $ federalPiscofinsTipoRetencao ,
324393 federalPiscofinsBaseCalculo: $ federalPiscofinsBaseCalculo ,
0 commit comments