@acontplus/ng-customer 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +365 -25
- package/fesm2022/acontplus-ng-customer.mjs +16 -16
- package/fesm2022/acontplus-ng-customer.mjs.map +1 -1
- package/index.d.ts +6 -6
- package/package.json +14 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @acontplus/ng-customer
|
|
2
2
|
|
|
3
|
-
Angular customer management library
|
|
3
|
+
Angular customer management library following clean architecture principles. Provides comprehensive customer CRUD operations with Angular Material UI components, domain-driven design patterns, and external SRI integration.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,42 +10,382 @@ npm install @acontplus/ng-customer
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **Use Cases**: Business logic for customer operations (
|
|
17
|
-
- **
|
|
18
|
-
- **DTOs and Mappers**: Data
|
|
19
|
-
- **
|
|
20
|
-
- **Angular Material
|
|
13
|
+
- **Clean Architecture**: Domain, Application, Infrastructure, and UI layers
|
|
14
|
+
- **Customer Components**: Card display and add/edit form components
|
|
15
|
+
- **SRI Integration**: External customer data validation and retrieval
|
|
16
|
+
- **Use Cases**: Business logic for customer operations (CRUD)
|
|
17
|
+
- **BaseRepository Pattern**: Data access abstractions with HTTP implementations
|
|
18
|
+
- **DTOs and Mappers**: Data transformation and mapping utilities
|
|
19
|
+
- **Form Validation**: Comprehensive validation including Ecuadorian ID/RUC
|
|
20
|
+
- **Angular Material**: Consistent Material Design components
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Quick Start
|
|
23
23
|
|
|
24
|
-
###
|
|
24
|
+
### 1. Import Components
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
```typescript
|
|
27
|
+
import { CustomerCard, CustomerAddEditComponent } from '@acontplus/ng-customer';
|
|
28
|
+
|
|
29
|
+
@Component({
|
|
30
|
+
selector: 'app-customers',
|
|
31
|
+
imports: [CustomerCard],
|
|
32
|
+
template: `
|
|
33
|
+
<acp-customer-card
|
|
34
|
+
[customer]="customer"
|
|
35
|
+
(editCustomer)="onEdit($event)"
|
|
36
|
+
(deleteCustomer)="onDelete($event)"
|
|
37
|
+
>
|
|
38
|
+
</acp-customer-card>
|
|
39
|
+
`,
|
|
40
|
+
})
|
|
41
|
+
export class CustomersComponent {
|
|
42
|
+
customer: CustomerListItemDto = {
|
|
43
|
+
idCliente: 1,
|
|
44
|
+
businessName: 'Acme Corp',
|
|
45
|
+
email: 'contact@acme.com',
|
|
46
|
+
phone: '123-456-7890',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
onEdit(customer: CustomerListItemDto) {
|
|
50
|
+
// Handle edit action
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
onDelete(customer: CustomerListItemDto) {
|
|
54
|
+
// Handle delete action
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Customer Add/Edit Dialog
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { MatDialog } from '@angular/material/dialog';
|
|
63
|
+
import { CustomerAddEditComponent } from '@acontplus/ng-customer';
|
|
64
|
+
|
|
65
|
+
@Component({...})
|
|
66
|
+
export class CustomerListComponent {
|
|
67
|
+
constructor(private dialog: MatDialog) {}
|
|
68
|
+
|
|
69
|
+
openCustomerDialog(customer?: any) {
|
|
70
|
+
const dialogRef = this.dialog.open(CustomerAddEditComponent, {
|
|
71
|
+
width: '800px',
|
|
72
|
+
data: {
|
|
73
|
+
id: customer?.id || 0,
|
|
74
|
+
descripcion: customer ? 'Editar Cliente' : 'Nuevo Cliente',
|
|
75
|
+
data: customer || {}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
dialogRef.afterClosed().subscribe(result => {
|
|
80
|
+
if (result) {
|
|
81
|
+
// Handle successful save
|
|
82
|
+
console.log('Customer saved:', result);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Architecture Layers
|
|
90
|
+
|
|
91
|
+
### Domain Layer
|
|
92
|
+
|
|
93
|
+
**Entities:**
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Customer entity with business logic
|
|
97
|
+
export class Customer {
|
|
98
|
+
constructor(
|
|
99
|
+
public readonly id: number,
|
|
100
|
+
public readonly businessName: string,
|
|
101
|
+
public readonly identificationNumber: string,
|
|
102
|
+
public readonly email?: string,
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
isValidForCredit(): boolean {
|
|
106
|
+
// Business logic for credit validation
|
|
107
|
+
return this.identificationNumber.length >= 10;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// External customer entity for SRI integration
|
|
112
|
+
export class CustomerExternal {
|
|
113
|
+
constructor(
|
|
114
|
+
public readonly idCard: string,
|
|
115
|
+
public readonly businessName: string,
|
|
116
|
+
public readonly address: string,
|
|
117
|
+
) {}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**BaseRepository Interfaces:**
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
export interface CustomerRepository {
|
|
125
|
+
getById(id: number): Promise<Customer>;
|
|
126
|
+
create(customer: CreateCustomerDto): Promise<Customer>;
|
|
127
|
+
update(id: number, customer: UpdateCustomerDto): Promise<Customer>;
|
|
128
|
+
delete(id: number): Promise<void>;
|
|
129
|
+
checkExistence(identificationNumber: string): Promise<boolean>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface CustomerExternalRepository {
|
|
133
|
+
getById(identificationNumber: string): Promise<CustomerExternal>;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Application Layer
|
|
138
|
+
|
|
139
|
+
**Use Cases:**
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Customer business operations
|
|
143
|
+
export class CustomerUseCase {
|
|
144
|
+
constructor(private repository: CustomerRepository) {}
|
|
145
|
+
|
|
146
|
+
async create(dto: CreateCustomerDto): Promise<ApiResponse<Customer>> {
|
|
147
|
+
// Business validation
|
|
148
|
+
if (!this.isValidIdentification(dto.numeroIdentificacion)) {
|
|
149
|
+
throw new Error('Invalid identification number');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return await this.repository.create(dto);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async getFormData(): Promise<ApiResponse<FormData>> {
|
|
156
|
+
// Load form dropdown data
|
|
157
|
+
return await this.repository.getFormData();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private isValidIdentification(id: string): boolean {
|
|
161
|
+
// Ecuadorian ID/RUC validation logic
|
|
162
|
+
return id.length >= 10;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// External SRI integration
|
|
167
|
+
export class CustomerExternalUseCase {
|
|
168
|
+
constructor(private repository: CustomerExternalRepository) {}
|
|
169
|
+
|
|
170
|
+
async getById(identificationNumber: string): Promise<ApiResponse<CustomerExternal>> {
|
|
171
|
+
return await this.repository.getById(identificationNumber);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Infrastructure Layer
|
|
177
|
+
|
|
178
|
+
**DTOs:**
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
export interface CustomerListItemDto {
|
|
182
|
+
idCliente: number;
|
|
183
|
+
businessName: string;
|
|
184
|
+
email?: string;
|
|
185
|
+
phone?: string;
|
|
186
|
+
identificationNumber?: string;
|
|
187
|
+
}
|
|
27
188
|
|
|
28
|
-
|
|
189
|
+
export interface CreateCustomerDto {
|
|
190
|
+
nombreFiscal: string;
|
|
191
|
+
nombreComercial?: string;
|
|
192
|
+
numeroIdentificacion: string;
|
|
193
|
+
idTipoIdentificacion: number;
|
|
194
|
+
direccion: string;
|
|
195
|
+
correo?: string;
|
|
196
|
+
telefono?: string;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**HTTP Repositories:**
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
export class CustomerHttpRepository implements CustomerRepository {
|
|
204
|
+
constructor(private http: HttpClient) {}
|
|
205
|
+
|
|
206
|
+
async getById(id: number): Promise<Customer> {
|
|
207
|
+
const response = await firstValueFrom(
|
|
208
|
+
this.http.get<ApiResponse<CustomerDto>>(`/api/customers/${id}`),
|
|
209
|
+
);
|
|
210
|
+
return CustomerMapper.toDomain(response.data);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async create(dto: CreateCustomerDto): Promise<Customer> {
|
|
214
|
+
const response = await firstValueFrom(
|
|
215
|
+
this.http.post<ApiResponse<CustomerDto>>('/api/customers', dto),
|
|
216
|
+
);
|
|
217
|
+
return CustomerMapper.toDomain(response.data);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Mappers:**
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
export class CustomerMapper {
|
|
226
|
+
static toDomain(dto: CustomerDto): Customer {
|
|
227
|
+
return new Customer(dto.idCliente, dto.nombreFiscal, dto.numeroIdentificacion, dto.correo);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
static toDto(customer: Customer): CustomerDto {
|
|
231
|
+
return {
|
|
232
|
+
idCliente: customer.id,
|
|
233
|
+
nombreFiscal: customer.businessName,
|
|
234
|
+
numeroIdentificacion: customer.identificationNumber,
|
|
235
|
+
correo: customer.email,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### UI Layer
|
|
242
|
+
|
|
243
|
+
**Customer Card Component:**
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
@Component({
|
|
247
|
+
selector: 'acp-customer-card',
|
|
248
|
+
template: `
|
|
249
|
+
<mat-card>
|
|
250
|
+
<mat-card-header>
|
|
251
|
+
<div mat-card-avatar class="customer-avatar">
|
|
252
|
+
{{ getLogoSliceBusinessName() }}
|
|
253
|
+
</div>
|
|
254
|
+
<mat-card-title>{{ customer().businessName }}</mat-card-title>
|
|
255
|
+
<mat-card-subtitle>{{ customer().email }}</mat-card-subtitle>
|
|
256
|
+
</mat-card-header>
|
|
257
|
+
|
|
258
|
+
<mat-card-content>
|
|
259
|
+
<p><strong>ID:</strong> {{ customer().identificationNumber }}</p>
|
|
260
|
+
<p><strong>Phone:</strong> {{ customer().phone }}</p>
|
|
261
|
+
</mat-card-content>
|
|
262
|
+
|
|
263
|
+
<mat-card-actions>
|
|
264
|
+
<button mat-button (click)="onEditClick()">Edit</button>
|
|
265
|
+
<button mat-button color="warn" (click)="onDeleteClick()">Delete</button>
|
|
266
|
+
</mat-card-actions>
|
|
267
|
+
</mat-card>
|
|
268
|
+
`,
|
|
269
|
+
})
|
|
270
|
+
export class CustomerCard {
|
|
271
|
+
customer = input.required<CustomerListItemDto>();
|
|
272
|
+
editCustomer = output<CustomerListItemDto>();
|
|
273
|
+
deleteCustomer = output<CustomerListItemDto>();
|
|
274
|
+
|
|
275
|
+
getLogoSliceBusinessName = computed(() => {
|
|
276
|
+
return this.customer()?.businessName?.slice(0, 1) || '?';
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
```
|
|
29
280
|
|
|
30
|
-
|
|
281
|
+
## Advanced Features
|
|
31
282
|
|
|
32
|
-
|
|
283
|
+
### SRI Integration
|
|
33
284
|
|
|
34
|
-
|
|
285
|
+
Automatic customer data retrieval from Ecuador's SRI (Tax Service):
|
|
35
286
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
287
|
+
```typescript
|
|
288
|
+
// Automatic SRI lookup on ID number input
|
|
289
|
+
onKeyDownGovernmentId() {
|
|
290
|
+
const identificationNumber = this.customerForm.get('numeroIdentificacion')?.value;
|
|
291
|
+
const identificationType = this.customerForm.get('idTipoIdentificacion')?.value;
|
|
292
|
+
|
|
293
|
+
if (identificationNumber && identificationType) {
|
|
294
|
+
this.customerExternalUseCase.getById(identificationNumber)
|
|
295
|
+
.subscribe(response => {
|
|
296
|
+
if (response.success) {
|
|
297
|
+
// Auto-fill form with SRI data
|
|
298
|
+
this.customerForm.patchValue({
|
|
299
|
+
nombreFiscal: response.data.businessName,
|
|
300
|
+
direccion: response.data.address,
|
|
301
|
+
validationSri: true
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Form Validation
|
|
310
|
+
|
|
311
|
+
Comprehensive validation including Ecuadorian ID/RUC:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// Custom validator for RUC format (must end with 001)
|
|
315
|
+
endsWith001Validator(control: AbstractControl): ValidationErrors | null {
|
|
316
|
+
const value = control.value;
|
|
317
|
+
if (value && value.length >= 3 && !value.endsWith('001')) {
|
|
318
|
+
return { endsWith001: true };
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Dynamic validation based on identification type
|
|
324
|
+
updateFormControlNumeroIdentificacion(codigoSri: string): void {
|
|
325
|
+
const idNumberControl = this.customerForm.get('numeroIdentificacion');
|
|
326
|
+
|
|
327
|
+
if (codigoSri === SRI_DOCUMENT_TYPE.CEDULA) {
|
|
328
|
+
idNumberControl?.setValidators([
|
|
329
|
+
Validators.required,
|
|
330
|
+
Validators.pattern(/^\d+$/),
|
|
331
|
+
Validators.minLength(10),
|
|
332
|
+
Validators.maxLength(10)
|
|
333
|
+
]);
|
|
334
|
+
} else if (codigoSri === SRI_DOCUMENT_TYPE.RUC) {
|
|
335
|
+
idNumberControl?.setValidators([
|
|
336
|
+
Validators.required,
|
|
337
|
+
Validators.pattern(/^\d+$/),
|
|
338
|
+
Validators.minLength(13),
|
|
339
|
+
Validators.maxLength(13),
|
|
340
|
+
this.endsWith001Validator
|
|
341
|
+
]);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
idNumberControl?.updateValueAndValidity();
|
|
345
|
+
}
|
|
346
|
+
```
|
|
40
347
|
|
|
41
|
-
|
|
348
|
+
### Multi-Contact Support
|
|
42
349
|
|
|
43
|
-
|
|
350
|
+
Handle multiple emails, phones, and license plates:
|
|
44
351
|
|
|
45
352
|
```typescript
|
|
46
|
-
|
|
353
|
+
// Arrays for multiple contact methods
|
|
354
|
+
emails: string[] = [];
|
|
355
|
+
telephones: string[] = [];
|
|
356
|
+
placas = signal<string[]>([]);
|
|
357
|
+
|
|
358
|
+
// Save with concatenated values
|
|
359
|
+
onSave() {
|
|
360
|
+
this.customerForm.patchValue({
|
|
361
|
+
telefono: this.telephones.join('|'),
|
|
362
|
+
correo: this.emails.join('|'),
|
|
363
|
+
placa: this.placas().join('|')
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Submit form...
|
|
367
|
+
}
|
|
47
368
|
```
|
|
48
369
|
|
|
49
|
-
##
|
|
370
|
+
## API Reference
|
|
371
|
+
|
|
372
|
+
### Components
|
|
373
|
+
|
|
374
|
+
- **CustomerCard**: Display customer information in card format
|
|
375
|
+
- **CustomerAddEditComponent**: Form for creating/editing customers
|
|
376
|
+
|
|
377
|
+
### Use Cases
|
|
378
|
+
|
|
379
|
+
- **CustomerUseCase**: Main customer business operations
|
|
380
|
+
- **CustomerExternalUseCase**: SRI integration operations
|
|
381
|
+
|
|
382
|
+
### Repositories
|
|
383
|
+
|
|
384
|
+
- **CustomerHttpRepository**: HTTP-based customer data access
|
|
385
|
+
- **CustomerExternalHttpRepository**: External SRI data access
|
|
386
|
+
|
|
387
|
+
### DTOs
|
|
50
388
|
|
|
51
|
-
|
|
389
|
+
- **CustomerListItemDto**: Customer list display data
|
|
390
|
+
- **CreateCustomerDto**: Customer creation data
|
|
391
|
+
- **CustomerExternalDto**: External SRI customer data
|