-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathFacturacion.Administrador.pas
321 lines (272 loc) · 11 KB
/
Facturacion.Administrador.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
{*******************************************************}
{ }
{ TFacturaElectronica }
{ }
{ Copyright (C) 2022 Bambu Code SA de CV }
{ }
{*******************************************************}
unit Facturacion.Administrador;
interface
uses Facturacion.Comprobante,
Facturacion.GeneradorCadenaOriginal,
Facturacion.GeneradorSello,
{$IF CompilerVersion >= 23}
System.SysUtils
{$ELSE}
SysUtils
{$IFEND}
;
type
/// <summary>
/// Evento lanzado una vez que se genera la cadena original durante el
/// proceso de sellado.
/// </summary>
TOnCadenaOriginalGeneradaEvent = procedure(Sender: TObject; const aCadenaOriginal: TCadenaUTF8) of Object;
/// <summary>
/// Evento lanzado una vez que se genera el sello exitosamente
/// </summary>
TOnSelloGeneradoEvent = procedure(Sender: TObject; const aSello: TCadenaUTF8) of Object;
EVersionDeCFDINoSoportadaException = class(Exception);
EXMLNoEsUnCFDIException = class(Exception);
/// <summary>
/// Instancia encargada de crear, sellar y guardar los comprobantes
/// fiscales.
/// </summary>
IAdministradorFacturas = Interface
['{F1F6F19F-E988-4F23-99D7-98935BEDEE6D}']
function GetOnCadenaOriginalGenerada: TOnCadenaOriginalGeneradaEvent;
function GetOnSelloGenerado: TOnSelloGeneradoEvent;
/// <summary>
/// Se encarga de crear una nueva instancia del CFDI según el parámetro
/// versión especificado
/// </summary>
/// <param name="aVersion">
/// Versión del CFDI, ejem: 3.3
/// </param>
/// <exception cref="EVersionDeCFDINoSoportadaException">
/// Excepcion lazanda cuando la versión especificada no está soportada o
/// implementada.
/// </exception>
function Nueva(const aVersion: String) : IComprobanteFiscal;
/// <summary>
/// Se encarga de sellar el comprobante solicitado con las respectivas
/// instancias del generador de cadena y sellador que se desee usar.
/// </summary>
/// <param name="aComprobante">
/// Instancia del CFDI a sellar
/// </param>
/// <param name="aGeneradorCadenaOriginal">
/// Instancia del generador de la cadena original, debe ser específico
/// para la versión del CFDI
/// </param>
/// <param name="aGeneradorSello">
/// Sellador del comprobante, debe ser específico para la versión del
/// CFDI
/// </param>
procedure Sellar(const aComprobante: IComprobanteFiscal; const
aGeneradorCadenaOriginal: IGeneradorCadenaOriginal; const aGeneradorSello:
IGeneradorSello);
/// <summary>
/// Método encargado de guardar el CFDI como archivo XML con su encoding
/// respectivo en UTF8.
/// </summary>
/// <param name="aComprobante">
/// Instancia del comprobante
/// </param>
/// <param name="aArchivoDestino">
/// Ruta completa con nombre incluido del archivo XML donde se guardará
/// el comprobante
/// </param>
procedure GuardarArchivo(const aComprobante: IComprobanteFiscal;
const aArchivoDestino: TFileName);
function LeerDesdeArchivo(const aRutaComprobante: TFileName) : IComprobanteFiscal;
function LeerDesdeXML(const aContenidoXML: UnicodeString) : IComprobanteFiscal;
procedure SetOnCadenaOriginalGenerada(const Value:
TOnCadenaOriginalGeneradaEvent);
procedure SetOnSelloGenerado(const Value: TOnSelloGeneradoEvent);
property OnSelloGenerado: TOnSelloGeneradoEvent read GetOnSelloGenerado write
SetOnSelloGenerado;
property OnCadenaOriginalGenerada: TOnCadenaOriginalGeneradaEvent read
GetOnCadenaOriginalGenerada write SetOnCadenaOriginalGenerada;
End;
// Implementación de la instancia
TAdministradorFacturas = class(TInterfacedObject, IAdministradorFacturas)
private
fOnCadenaOriginalGenerada: TOnCadenaOriginalGeneradaEvent;
fOnSelloGenerado: TOnSelloGeneradoEvent;
function GetOnCadenaOriginalGenerada: TOnCadenaOriginalGeneradaEvent;
function GetOnSelloGenerado: TOnSelloGeneradoEvent;
procedure SetOnCadenaOriginalGenerada(const Value:TOnCadenaOriginalGeneradaEvent);
procedure SetOnSelloGenerado(const Value: TOnSelloGeneradoEvent);
public
function Nueva(const aVersion: String) : IComprobanteFiscal;
procedure Sellar(const aComprobante: IComprobanteFiscal;
const aGeneradorCadenaOriginal: IGeneradorCadenaOriginal;
const aGeneradorSello: IGeneradorSello);
procedure GuardarArchivo(const aComprobante: IComprobanteFiscal;
const aArchivoDestino: TFileName);
function LeerDesdeArchivo(const aRutaComprobante: TFileName) : IComprobanteFiscal;
function LeerDesdeXML(const aContenidoXML: UnicodeString) : IComprobanteFiscal;
property OnSelloGenerado: TOnSelloGeneradoEvent read GetOnSelloGenerado
write SetOnSelloGenerado;
property OnCadenaOriginalGenerada: TOnCadenaOriginalGeneradaEvent read GetOnCadenaOriginalGenerada
write SetOnCadenaOriginalGenerada;
end;
implementation
uses
{$IF CompilerVersion >= 23}
System.Classes,
Xml.XMLDoc,
Xml.XMLIntf,
{$ELSE}
Classes,
XMLDoc,
XMLIntf,
{$IFEND}
Facturacion.ComprobanteV32,
Facturacion.ComprobanteV33,
Facturacion.ComprobanteV40
{$IFDEF CODESITE}
, CodeSiteLogging
{$ENDIF}
;
{ TAdministradorFacturas }
function TAdministradorFacturas.Nueva(const aVersion: String): IComprobanteFiscal;
begin
Result := nil;
if aVersion = '3.2' then
Result := NewComprobanteFiscalV32;
if aVersion = '3.3' then
Result := NewComprobanteFiscalV33;
if aVersion = '4.0' then
Result := NewComprobanteFiscalV40;
if Result = nil then
raise EVersionDeCFDINoSoportadaException.Create('La versión solicitada : ' + aVersion + ', no tiene implementación actual');
end;
procedure TAdministradorFacturas.Sellar(const aComprobante: IComprobanteFiscal;
const aGeneradorCadenaOriginal: IGeneradorCadenaOriginal; const
aGeneradorSello: IGeneradorSello);
var
cadenaOriginal, selloDeFactura: TCadenaUTF8;
begin
Assert(aGeneradorCadenaOriginal <> nil, 'La instancia aGeneradorCadenaOriginal no debio ser nula');
Assert(aGeneradorSello <> nil, 'La instancia aGeneradorSello no debio ser nula');
// 1. Intentamos obtener la cadena Original del comprobante
cadenaOriginal := aGeneradorCadenaOriginal.ObtenerCadenaOriginal(aComprobante);
// Si tenemos asignado el evento de notificacion de cadena original, lo lanzamos...
if Assigned(fOnCadenaOriginalGenerada) then
fOnCadenaOriginalGenerada(Self, cadenaOriginal);
// 2. Al tener la cadena original mandamos sellar la factura
selloDeFactura := aGeneradorSello.GenerarSelloDeFactura(cadenaOriginal);
// Si tenemos asignado el evento de notificacion de sello, lo lanzamos
if Assigned(fOnSelloGenerado) then
fOnSelloGenerado(Self, selloDeFactura);
// 3. Anexamos el sello al comprobante
aComprobante.Sello := selloDeFactura;
end;
procedure TAdministradorFacturas.GuardarArchivo(const aComprobante: IComprobanteFiscal;
const aArchivoDestino: TFileName);
var
{$IF CompilerVersion >= 20}
Writer: TStreamWriter;
{$ELSE}
XML: IXMLDocument;
{$IFEND}
const
_ENCABEZADO_XML = '<?xml version="1.0" encoding="utf-8"?>' + #13#10;
begin
{$IF CompilerVersion >= 20}
Writer := TStreamWriter.Create(aArchivoDestino, false, TEncoding.UTF8);
{$ELSE}
XML := NewXMLDocument;
{$IFEND}
try
// Forzamos a que siempre se incluya el encabezado del XML
{$IF CompilerVersion >= 20}
Writer.Write(_ENCABEZADO_XML + aComprobante.XML);
{$ELSE}
XML.LoadFromXML( _ENCABEZADO_XML + aComprobante.XML );
//Esto es necesario para que el Encoding UTF-8 se guarde en el archivo
Xml.Options := Xml.Options + [doNodeAutoIndent];
Xml.ParseOptions := Xml.ParseOptions + [poPreserveWhiteSpace];
Xml.Encoding := 'UTF-8';
XML.SaveToFile(aArchivoDestino);
{$IFEND}
finally
{$IF CompilerVersion >= 20}
Writer.Free();
{$ELSE}
{$IFEND}
end;
end;
function TAdministradorFacturas.LeerDesdeArchivo(const aRutaComprobante: TFileName): IComprobanteFiscal;
var
documentoXML: IXMLDocument;
begin
documentoXML := LoadXMLDocument(aRutaComprobante);
{$IF CompilerVersion >= 20}
Result := LeerDesdeXML( documentoXML.XML.Text );
{$ELSE}
documentoXML.Options := documentoXML.Options + [doNodeAutoIndent];
documentoXML.Encoding := 'UTF-8';
Result := LeerDesdeXML( UTF8Decode( documentoXML.XML.Text ) );
{$IFEND}
end;
function TAdministradorFacturas.LeerDesdeXML(const aContenidoXML: UnicodeString): IComprobanteFiscal;
var
documentoXML: IXMLDocument;
nodoComprobante, nodoVersion: IXMLNode;
versionCFDI: TCadenaUTF8;
const
_NOMBRE_NODO_COMPROBANTE = 'Comprobante';
_NOMBRE_NODO_VERSION = 'Version';
begin
documentoXML := TXMLDocument.Create(nil);
// Pasamos el XML para poder usarlo en la clase
documentoXML.LoadFromXML(aContenidoXML);
// Checamos la version del CFDI y dependiendo de ello, usamos el método
// de lectura correcto
nodoComprobante := documentoXML.ChildNodes.FindNode(_NOMBRE_NODO_COMPROBANTE);
if (nodoComprobante <> nil) then
begin
// Intentamos obtener el nodo Version en el "case correcto"
nodoVersion := nodoComprobante.AttributeNodes.FindNode(_NOMBRE_NODO_VERSION);
// Intentamos con el atributo en minusculas solamente...
if nodoVersion = nil then
nodoVersion := nodoComprobante.AttributeNodes.FindNode(LowerCase(_NOMBRE_NODO_VERSION));
if (nodoVersion <> nil) then
begin
versionCFDI := Trim(nodoVersion.Text);
// Mandamos leer el XML usando la implementación correspondiente
if (versionCFDI = '3.2') then
Result := GetComprobanteFiscalV32(documentoXML);
if (versionCFDI = '3.3') then
Result := GetComprobanteFiscalV33(documentoXML);
if (versionCFDI = '4.0') then
Result := GetComprobanteFiscalV40(documentoXML);
end;
if Result = nil then
raise EVersionDeCFDINoSoportadaException.Create('No es posible leer un CFDI ver. ' + versionCFDI);
end else
raise EXMLNoEsUnCFDIException.Create('El XML no parece ser un CFDI, no contiene nodo Comprobante');
end;
function TAdministradorFacturas.GetOnCadenaOriginalGenerada:
TOnCadenaOriginalGeneradaEvent;
begin
Result := fOnCadenaOriginalGenerada;
end;
procedure TAdministradorFacturas.SetOnCadenaOriginalGenerada(const Value:
TOnCadenaOriginalGeneradaEvent);
begin
fOnCadenaOriginalGenerada := Value;
end;
function TAdministradorFacturas.GetOnSelloGenerado: TOnSelloGeneradoEvent;
begin
Result := fOnSelloGenerado;
end;
procedure TAdministradorFacturas.SetOnSelloGenerado(const Value:
TOnSelloGeneradoEvent);
begin
fOnSelloGenerado := Value;
end;
end.