Compare commits
2 Commits
7962420565
...
5d0dc3b367
| Author | SHA1 | Date |
|---|---|---|
|
|
5d0dc3b367 | |
|
|
c38a786f89 |
|
|
@ -398,9 +398,9 @@
|
||||||
<td>
|
<td>
|
||||||
<div class="action-group justify-content-center">
|
<div class="action-group justify-content-center">
|
||||||
<button class="btn-icon" (click)="onDetalhes(r)" title="Detalhes"><i class="bi bi-eye"></i></button>
|
<button class="btn-icon" (click)="onDetalhes(r)" title="Detalhes"><i class="bi bi-eye"></i></button>
|
||||||
|
<button class="btn-icon primary" (click)="onEditar(r)" title="Editar"><i class="bi bi-pencil-square"></i></button>
|
||||||
<ng-container *ngIf="!isClientRestricted">
|
<ng-container *ngIf="!isClientRestricted">
|
||||||
<button class="btn-icon success" (click)="onFinanceiro(r)" title="Financeiro"><i class="bi bi-cash-coin"></i></button>
|
<button class="btn-icon success" (click)="onFinanceiro(r)" title="Financeiro"><i class="bi bi-cash-coin"></i></button>
|
||||||
<button class="btn-icon primary" (click)="onEditar(r)" title="Editar"><i class="bi bi-pencil-square"></i></button>
|
|
||||||
<button *ngIf="isSysAdmin" class="btn-icon danger" (click)="onRemover(r, true)" title="Remover"><i class="bi bi-trash"></i></button>
|
<button *ngIf="isSysAdmin" class="btn-icon danger" (click)="onRemover(r, true)" title="Remover"><i class="bi bi-trash"></i></button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -517,9 +517,9 @@
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="action-group justify-content-center">
|
<div class="action-group justify-content-center">
|
||||||
<button class="btn-icon" (click)="onDetalhes(r)" title="Detalhes"><i class="bi bi-eye"></i></button>
|
<button class="btn-icon" (click)="onDetalhes(r)" title="Detalhes"><i class="bi bi-eye"></i></button>
|
||||||
|
<button class="btn-icon primary" (click)="onEditar(r)" title="Editar"><i class="bi bi-pencil-square"></i></button>
|
||||||
<ng-container *ngIf="!isClientRestricted">
|
<ng-container *ngIf="!isClientRestricted">
|
||||||
<button class="btn-icon success" (click)="onFinanceiro(r)" title="Financeiro"><i class="bi bi-cash-coin"></i></button>
|
<button class="btn-icon success" (click)="onFinanceiro(r)" title="Financeiro"><i class="bi bi-cash-coin"></i></button>
|
||||||
<button class="btn-icon primary" (click)="onEditar(r)" title="Editar"><i class="bi bi-pencil-square"></i></button>
|
|
||||||
<button *ngIf="isSysAdmin" class="btn-icon danger" (click)="onRemover(r)" title="Remover"><i class="bi bi-trash"></i></button>
|
<button *ngIf="isSysAdmin" class="btn-icon danger" (click)="onRemover(r)" title="Remover"><i class="bi bi-trash"></i></button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1693,8 +1693,8 @@
|
||||||
|
|
||||||
<div class="modal-body modern-body bg-light-gray" *ngIf="detailData; else detailLoading">
|
<div class="modal-body modern-body bg-light-gray" *ngIf="detailData; else detailLoading">
|
||||||
<div class="details-dashboard">
|
<div class="details-dashboard">
|
||||||
<div class="dashboard-column">
|
<ng-template #detailIdentificacaoCard>
|
||||||
<div class="detail-box h-100">
|
<div class="detail-box">
|
||||||
<div class="box-header justify-content-center">
|
<div class="box-header justify-content-center">
|
||||||
<span><i class="bi bi-person-badge me-2"></i> Identificação</span>
|
<span><i class="bi bi-person-badge me-2"></i> Identificação</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1712,9 +1712,13 @@
|
||||||
<span class="lbl">Usuário</span>
|
<span class="lbl">Usuário</span>
|
||||||
<span class="val">{{ detailData.usuario || '-' }}</span>
|
<span class="val">{{ detailData.usuario || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item span-2" *ngIf="isClientRestricted">
|
<div class="info-item span-2">
|
||||||
<span class="lbl">Centro de Custos</span>
|
<span class="lbl">Centro de Custos</span>
|
||||||
<span class="val text-dark"> </span>
|
<span class="val text-dark">{{ detailData.centroDeCustos || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item span-2">
|
||||||
|
<span class="lbl">Setor</span>
|
||||||
|
<span class="val">{{ detailData.setorNome || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="lbl">Item</span>
|
<span class="lbl">Item</span>
|
||||||
|
|
@ -1735,6 +1739,69 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #detailAparelhoCard>
|
||||||
|
<div class="detail-box">
|
||||||
|
<div class="box-header justify-content-center">
|
||||||
|
<span><i class="bi bi-phone me-2"></i> Aparelho</span>
|
||||||
|
</div>
|
||||||
|
<div class="box-body compact-padding">
|
||||||
|
<div class="info-grid compact-gap">
|
||||||
|
<div class="info-item span-2">
|
||||||
|
<span class="lbl">Aparelho</span>
|
||||||
|
<span class="val">{{ detailData.aparelhoNome || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="lbl">Cor do Aparelho</span>
|
||||||
|
<span class="val">{{ detailData.aparelhoCor || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="lbl">IMEI</span>
|
||||||
|
<span class="val">{{ detailData.aparelhoImei || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item span-2">
|
||||||
|
<span class="lbl">Nota Fiscal (Anexo)</span>
|
||||||
|
<span class="val">
|
||||||
|
<ng-container *ngIf="detailData.aparelhoNotaFiscalTemArquivo; else noNotaFiscalAnexo">
|
||||||
|
<button class="btn btn-sm btn-outline-primary" (click)="downloadAparelhoAnexo('nota-fiscal', detailData.id)">
|
||||||
|
Baixar arquivo
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #noNotaFiscalAnexo>
|
||||||
|
-
|
||||||
|
</ng-template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item span-2">
|
||||||
|
<span class="lbl">Recibo (Anexo)</span>
|
||||||
|
<span class="val">
|
||||||
|
<ng-container *ngIf="detailData.aparelhoReciboTemArquivo; else noReciboAnexo">
|
||||||
|
<button class="btn btn-sm btn-outline-primary" (click)="downloadAparelhoAnexo('recibo', detailData.id)">
|
||||||
|
Baixar arquivo
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #noReciboAnexo>
|
||||||
|
-
|
||||||
|
</ng-template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div class="dashboard-column d-flex flex-column gap-2" *ngIf="!isClientRestricted">
|
||||||
|
<ng-container *ngTemplateOutlet="detailIdentificacaoCard"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="detailAparelhoCard"></ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dashboard-column" *ngIf="isClientRestricted">
|
||||||
|
<ng-container *ngTemplateOutlet="detailIdentificacaoCard"></ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dashboard-column" *ngIf="isClientRestricted">
|
||||||
|
<ng-container *ngTemplateOutlet="detailAparelhoCard"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dashboard-column d-flex flex-column gap-2" *ngIf="!isClientRestricted">
|
<div class="dashboard-column d-flex flex-column gap-2" *ngIf="!isClientRestricted">
|
||||||
|
|
@ -1919,7 +1986,85 @@
|
||||||
|
|
||||||
<div class="modal-body modern-body bg-light-gray">
|
<div class="modal-body modern-body bg-light-gray">
|
||||||
<ng-container *ngIf="editModel; else editLoadingTpl">
|
<ng-container *ngIf="editModel; else editLoadingTpl">
|
||||||
<div class="edit-sections">
|
<div class="edit-sections" *ngIf="isClientRestricted">
|
||||||
|
<details open class="detail-box">
|
||||||
|
<summary class="box-header">
|
||||||
|
<span><i class="bi bi-person-badge me-2"></i> Identificação</span>
|
||||||
|
<i class="bi bi-chevron-down ms-auto transition-icon"></i>
|
||||||
|
</summary>
|
||||||
|
<div class="box-body">
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-field">
|
||||||
|
<label>Item</label>
|
||||||
|
<input class="form-control form-control-sm bg-light" type="number" [(ngModel)]="editModel.item" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label>Linha</label>
|
||||||
|
<input class="form-control form-control-sm bg-light" [(ngModel)]="editModel.linha" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label>Usuário</label>
|
||||||
|
<input class="form-control form-control-sm" [(ngModel)]="editModel.usuario" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label>Centro de Custos</label>
|
||||||
|
<input class="form-control form-control-sm" [(ngModel)]="editModel.centroDeCustos" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details open class="detail-box">
|
||||||
|
<summary class="box-header">
|
||||||
|
<span><i class="bi bi-phone me-2"></i> Aparelho</span>
|
||||||
|
<i class="bi bi-chevron-down ms-auto transition-icon"></i>
|
||||||
|
</summary>
|
||||||
|
<div class="box-body">
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-field span-2">
|
||||||
|
<label>Aparelho (Nome)</label>
|
||||||
|
<input class="form-control form-control-sm" [(ngModel)]="editModel.aparelhoNome" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label>Cor do Aparelho</label>
|
||||||
|
<input class="form-control form-control-sm" [(ngModel)]="editModel.aparelhoCor" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label>IMEI</label>
|
||||||
|
<input class="form-control form-control-sm" [(ngModel)]="editModel.aparelhoImei" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field span-2">
|
||||||
|
<label>Nota Fiscal (Arquivo)</label>
|
||||||
|
<input class="form-control form-control-sm" type="file" accept=".pdf,.png,.jpg,.jpeg,.webp" (change)="onAparelhoNotaFiscalSelected($event)" />
|
||||||
|
<small class="text-muted d-block mt-1" *ngIf="aparelhoNotaFiscalFile">Selecionado: {{ aparelhoNotaFiscalFile.name }}</small>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline-primary mt-2"
|
||||||
|
type="button"
|
||||||
|
*ngIf="!aparelhoNotaFiscalFile && editModel.aparelhoNotaFiscalTemArquivo"
|
||||||
|
(click)="downloadAparelhoAnexo('nota-fiscal', editModel?.id)"
|
||||||
|
>
|
||||||
|
Baixar arquivo atual
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-field span-2">
|
||||||
|
<label>Recibo (Arquivo)</label>
|
||||||
|
<input class="form-control form-control-sm" type="file" accept=".pdf,.png,.jpg,.jpeg,.webp" (change)="onAparelhoReciboSelected($event)" />
|
||||||
|
<small class="text-muted d-block mt-1" *ngIf="aparelhoReciboFile">Selecionado: {{ aparelhoReciboFile.name }}</small>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline-primary mt-2"
|
||||||
|
type="button"
|
||||||
|
*ngIf="!aparelhoReciboFile && editModel.aparelhoReciboTemArquivo"
|
||||||
|
(click)="downloadAparelhoAnexo('recibo', editModel?.id)"
|
||||||
|
>
|
||||||
|
Baixar arquivo atual
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="edit-sections" *ngIf="!isClientRestricted">
|
||||||
<details open class="detail-box">
|
<details open class="detail-box">
|
||||||
<summary class="box-header"><span><i class="bi bi-person-badge me-2"></i> Identificação</span><i class="bi bi-chevron-down ms-auto transition-icon"></i></summary>
|
<summary class="box-header"><span><i class="bi bi-person-badge me-2"></i> Identificação</span><i class="bi bi-chevron-down ms-auto transition-icon"></i></summary>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
|
|
@ -1932,6 +2077,45 @@
|
||||||
<div class="form-field"><label>Tipo de Chip</label><input class="form-control form-control-sm" [(ngModel)]="editModel.tipoDeChip" /></div>
|
<div class="form-field"><label>Tipo de Chip</label><input class="form-control form-control-sm" [(ngModel)]="editModel.tipoDeChip" /></div>
|
||||||
<div class="form-field"><label>Cliente</label><input class="form-control form-control-sm" [(ngModel)]="editModel.cliente" /></div>
|
<div class="form-field"><label>Cliente</label><input class="form-control form-control-sm" [(ngModel)]="editModel.cliente" /></div>
|
||||||
<div class="form-field"><label>Usuário</label><input class="form-control form-control-sm" [(ngModel)]="editModel.usuario" /></div>
|
<div class="form-field"><label>Usuário</label><input class="form-control form-control-sm" [(ngModel)]="editModel.usuario" /></div>
|
||||||
|
<div class="form-field"><label>Centro de Custos</label><input class="form-control form-control-sm" [(ngModel)]="editModel.centroDeCustos" /></div>
|
||||||
|
<div class="form-field"><label>Setor</label><input class="form-control form-control-sm" [(ngModel)]="editModel.setorNome" /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details open class="detail-box">
|
||||||
|
<summary class="box-header"><span><i class="bi bi-phone me-2"></i> Aparelho</span><i class="bi bi-chevron-down ms-auto transition-icon"></i></summary>
|
||||||
|
<div class="box-body">
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-field span-2"><label>Aparelho (Nome)</label><input class="form-control form-control-sm" [(ngModel)]="editModel.aparelhoNome" /></div>
|
||||||
|
<div class="form-field"><label>Aparelho (Cor)</label><input class="form-control form-control-sm" [(ngModel)]="editModel.aparelhoCor" /></div>
|
||||||
|
<div class="form-field"><label>Aparelho (IMEI)</label><input class="form-control form-control-sm" [(ngModel)]="editModel.aparelhoImei" /></div>
|
||||||
|
<div class="form-field span-2">
|
||||||
|
<label>Nota Fiscal (Arquivo)</label>
|
||||||
|
<input class="form-control form-control-sm" type="file" accept=".pdf,.png,.jpg,.jpeg,.webp" (change)="onAparelhoNotaFiscalSelected($event)" />
|
||||||
|
<small class="text-muted d-block mt-1" *ngIf="aparelhoNotaFiscalFile">Selecionado: {{ aparelhoNotaFiscalFile.name }}</small>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline-primary mt-2"
|
||||||
|
type="button"
|
||||||
|
*ngIf="!aparelhoNotaFiscalFile && editModel.aparelhoNotaFiscalTemArquivo"
|
||||||
|
(click)="downloadAparelhoAnexo('nota-fiscal', editModel?.id)"
|
||||||
|
>
|
||||||
|
Baixar arquivo atual
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-field span-2">
|
||||||
|
<label>Recibo (Arquivo)</label>
|
||||||
|
<input class="form-control form-control-sm" type="file" accept=".pdf,.png,.jpg,.jpeg,.webp" (change)="onAparelhoReciboSelected($event)" />
|
||||||
|
<small class="text-muted d-block mt-1" *ngIf="aparelhoReciboFile">Selecionado: {{ aparelhoReciboFile.name }}</small>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline-primary mt-2"
|
||||||
|
type="button"
|
||||||
|
*ngIf="!aparelhoReciboFile && editModel.aparelhoReciboTemArquivo"
|
||||||
|
(click)="downloadAparelhoAnexo('recibo', editModel?.id)"
|
||||||
|
>
|
||||||
|
Baixar arquivo atual
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
|
||||||
|
|
@ -518,12 +518,18 @@
|
||||||
.modal-body .box-body { overflow: visible; }
|
.modal-body .box-body { overflow: visible; }
|
||||||
.modal-xl-custom { width: min(1100px, 95vw); max-height: 85vh; }
|
.modal-xl-custom { width: min(1100px, 95vw); max-height: 85vh; }
|
||||||
.modal-card.modal-client-detail {
|
.modal-card.modal-client-detail {
|
||||||
width: min(560px, 95vw);
|
width: min(980px, 96vw);
|
||||||
}
|
}
|
||||||
.modal-card.modal-client-detail .details-dashboard {
|
.modal-card.modal-client-detail .details-dashboard {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
max-width: 520px;
|
gap: 14px;
|
||||||
|
max-width: 940px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
max-width: 520px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.modal-card.modal-create { width: min(1280px, 96vw); max-height: 92vh; }
|
.modal-card.modal-create { width: min(1280px, 96vw); max-height: 92vh; }
|
||||||
.modal-card.modal-create.batch-mode { width: min(1560px, 99vw); }
|
.modal-card.modal-create.batch-mode { width: min(1560px, 99vw); }
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,10 @@ interface LineRow {
|
||||||
chip?: string;
|
chip?: string;
|
||||||
cliente: string;
|
cliente: string;
|
||||||
usuario: string;
|
usuario: string;
|
||||||
|
centroDeCustos?: string;
|
||||||
|
setorNome?: string;
|
||||||
|
aparelhoNome?: string;
|
||||||
|
aparelhoCor?: string;
|
||||||
status: string;
|
status: string;
|
||||||
skil: string;
|
skil: string;
|
||||||
contrato: string;
|
contrato: string;
|
||||||
|
|
@ -68,6 +72,10 @@ interface ApiLineList {
|
||||||
chip?: string | null;
|
chip?: string | null;
|
||||||
cliente: string | null;
|
cliente: string | null;
|
||||||
usuario: string | null;
|
usuario: string | null;
|
||||||
|
centroDeCustos?: string | null;
|
||||||
|
setorNome?: string | null;
|
||||||
|
aparelhoNome?: string | null;
|
||||||
|
aparelhoCor?: string | null;
|
||||||
vencConta: string | null;
|
vencConta: string | null;
|
||||||
status?: string | null;
|
status?: string | null;
|
||||||
skil?: string | null;
|
skil?: string | null;
|
||||||
|
|
@ -89,6 +97,15 @@ interface ApiLineDetail {
|
||||||
tipoDeChip?: string | null;
|
tipoDeChip?: string | null;
|
||||||
cliente?: string | null;
|
cliente?: string | null;
|
||||||
usuario?: string | null;
|
usuario?: string | null;
|
||||||
|
centroDeCustos?: string | null;
|
||||||
|
setorId?: string | null;
|
||||||
|
setorNome?: string | null;
|
||||||
|
aparelhoId?: string | null;
|
||||||
|
aparelhoNome?: string | null;
|
||||||
|
aparelhoCor?: string | null;
|
||||||
|
aparelhoImei?: string | null;
|
||||||
|
aparelhoNotaFiscalTemArquivo?: boolean;
|
||||||
|
aparelhoReciboTemArquivo?: boolean;
|
||||||
planoContrato?: string | null;
|
planoContrato?: string | null;
|
||||||
status?: string | null;
|
status?: string | null;
|
||||||
skil?: string | null;
|
skil?: string | null;
|
||||||
|
|
@ -365,6 +382,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
||||||
detailData: any = null;
|
detailData: any = null;
|
||||||
financeData: any = null;
|
financeData: any = null;
|
||||||
editModel: any = null;
|
editModel: any = null;
|
||||||
|
aparelhoNotaFiscalFile: File | null = null;
|
||||||
|
aparelhoReciboFile: File | null = null;
|
||||||
|
|
||||||
private editingId: string | null = null;
|
private editingId: string | null = null;
|
||||||
private searchTimer: any = null;
|
private searchTimer: any = null;
|
||||||
|
|
@ -835,6 +854,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
this.detailData = null;
|
this.detailData = null;
|
||||||
this.financeData = null;
|
this.financeData = null;
|
||||||
|
this.aparelhoNotaFiscalFile = null;
|
||||||
|
this.aparelhoReciboFile = null;
|
||||||
|
|
||||||
this.editSaving = false;
|
this.editSaving = false;
|
||||||
this.createSaving = false;
|
this.createSaving = false;
|
||||||
|
|
@ -1609,6 +1630,10 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
||||||
chip: x.chip ?? '',
|
chip: x.chip ?? '',
|
||||||
cliente: x.cliente ?? '',
|
cliente: x.cliente ?? '',
|
||||||
usuario: x.usuario ?? '',
|
usuario: x.usuario ?? '',
|
||||||
|
centroDeCustos: x.centroDeCustos ?? '',
|
||||||
|
setorNome: x.setorNome ?? '',
|
||||||
|
aparelhoNome: x.aparelhoNome ?? '',
|
||||||
|
aparelhoCor: x.aparelhoCor ?? '',
|
||||||
status: x.status ?? '',
|
status: x.status ?? '',
|
||||||
skil: x.skil ?? '',
|
skil: x.skil ?? '',
|
||||||
contrato: x.vencConta ?? ''
|
contrato: x.vencConta ?? ''
|
||||||
|
|
@ -1841,6 +1866,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
||||||
this.editOpen = true;
|
this.editOpen = true;
|
||||||
this.editSaving = false;
|
this.editSaving = false;
|
||||||
this.editModel = null;
|
this.editModel = null;
|
||||||
|
this.aparelhoNotaFiscalFile = null;
|
||||||
|
this.aparelhoReciboFile = null;
|
||||||
this.editingId = r.id;
|
this.editingId = r.id;
|
||||||
|
|
||||||
this.cdr.detectChanges();
|
this.cdr.detectChanges();
|
||||||
|
|
@ -1918,45 +1945,71 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
||||||
if (!this.editingId || !this.editModel) return;
|
if (!this.editingId || !this.editModel) return;
|
||||||
|
|
||||||
this.editSaving = true;
|
this.editSaving = true;
|
||||||
this.calculateFinancials(this.editModel);
|
const editingId = this.editingId;
|
||||||
|
const shouldUploadAttachments = !!(this.aparelhoNotaFiscalFile || this.aparelhoReciboFile);
|
||||||
|
let payload: UpdateMobileLineRequest;
|
||||||
|
|
||||||
const { contaEmpresa: _contaEmpresa, ...editModelPayload } = this.editModel;
|
if (this.isClientRestricted) {
|
||||||
|
payload = {
|
||||||
|
item: this.toInt(this.editModel.item),
|
||||||
|
usuario: (this.editModel.usuario ?? '').toString(),
|
||||||
|
centroDeCustos: (this.editModel.centroDeCustos ?? '').toString(),
|
||||||
|
aparelhoId: (this.editModel.aparelhoId ?? null) as string | null,
|
||||||
|
aparelhoNome: (this.editModel.aparelhoNome ?? '').toString(),
|
||||||
|
aparelhoCor: (this.editModel.aparelhoCor ?? '').toString(),
|
||||||
|
aparelhoImei: (this.editModel.aparelhoImei ?? '').toString()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.calculateFinancials(this.editModel);
|
||||||
|
|
||||||
const payload: UpdateMobileLineRequest = {
|
const { contaEmpresa: _contaEmpresa, ...editModelPayload } = this.editModel;
|
||||||
...editModelPayload,
|
|
||||||
item: this.toInt(this.editModel.item),
|
|
||||||
dataBloqueio: this.dateInputToIso(this.editModel.dataBloqueio),
|
|
||||||
dataEntregaOpera: this.dateInputToIso(this.editModel.dataEntregaOpera),
|
|
||||||
dataEntregaCliente: this.dateInputToIso(this.editModel.dataEntregaCliente),
|
|
||||||
dtEfetivacaoServico: this.dateInputToIso(this.editModel.dtEfetivacaoServico),
|
|
||||||
dtTerminoFidelizacao: this.dateInputToIso(this.editModel.dtTerminoFidelizacao),
|
|
||||||
vencConta: (this.editModel.vencConta ?? '').toString(),
|
|
||||||
franquiaVivo: this.toNullableNumber(this.editModel.franquiaVivo),
|
|
||||||
valorPlanoVivo: this.toNullableNumber(this.editModel.valorPlanoVivo),
|
|
||||||
gestaoVozDados: this.toNullableNumber(this.editModel.gestaoVozDados),
|
|
||||||
skeelo: this.toNullableNumber(this.editModel.skeelo),
|
|
||||||
vivoNewsPlus: this.toNullableNumber(this.editModel.vivoNewsPlus),
|
|
||||||
vivoTravelMundo: this.toNullableNumber(this.editModel.vivoTravelMundo),
|
|
||||||
vivoGestaoDispositivo: this.toNullableNumber(this.editModel.vivoGestaoDispositivo),
|
|
||||||
vivoSync: this.toNullableNumber(this.editModel.vivoSync),
|
|
||||||
valorContratoVivo: this.toNullableNumber(this.editModel.valorContratoVivo),
|
|
||||||
franquiaLine: this.toNullableNumber(this.editModel.franquiaLine),
|
|
||||||
franquiaGestao: this.toNullableNumber(this.editModel.franquiaGestao),
|
|
||||||
locacaoAp: this.toNullableNumber(this.editModel.locacaoAp),
|
|
||||||
valorContratoLine: this.toNullableNumber(this.editModel.valorContratoLine),
|
|
||||||
desconto: this.toNullableNumber(this.editModel.desconto),
|
|
||||||
lucro: this.toNullableNumber(this.editModel.lucro),
|
|
||||||
tipoDeChip: (this.editModel.tipoDeChip ?? '').toString()
|
|
||||||
};
|
|
||||||
|
|
||||||
this.http.put(`${this.apiBase}/${this.editingId}`, payload).subscribe({
|
payload = {
|
||||||
|
...editModelPayload,
|
||||||
|
item: this.toInt(this.editModel.item),
|
||||||
|
dataBloqueio: this.dateInputToIso(this.editModel.dataBloqueio),
|
||||||
|
dataEntregaOpera: this.dateInputToIso(this.editModel.dataEntregaOpera),
|
||||||
|
dataEntregaCliente: this.dateInputToIso(this.editModel.dataEntregaCliente),
|
||||||
|
dtEfetivacaoServico: this.dateInputToIso(this.editModel.dtEfetivacaoServico),
|
||||||
|
dtTerminoFidelizacao: this.dateInputToIso(this.editModel.dtTerminoFidelizacao),
|
||||||
|
vencConta: (this.editModel.vencConta ?? '').toString(),
|
||||||
|
franquiaVivo: this.toNullableNumber(this.editModel.franquiaVivo),
|
||||||
|
valorPlanoVivo: this.toNullableNumber(this.editModel.valorPlanoVivo),
|
||||||
|
gestaoVozDados: this.toNullableNumber(this.editModel.gestaoVozDados),
|
||||||
|
skeelo: this.toNullableNumber(this.editModel.skeelo),
|
||||||
|
vivoNewsPlus: this.toNullableNumber(this.editModel.vivoNewsPlus),
|
||||||
|
vivoTravelMundo: this.toNullableNumber(this.editModel.vivoTravelMundo),
|
||||||
|
vivoGestaoDispositivo: this.toNullableNumber(this.editModel.vivoGestaoDispositivo),
|
||||||
|
vivoSync: this.toNullableNumber(this.editModel.vivoSync),
|
||||||
|
valorContratoVivo: this.toNullableNumber(this.editModel.valorContratoVivo),
|
||||||
|
franquiaLine: this.toNullableNumber(this.editModel.franquiaLine),
|
||||||
|
franquiaGestao: this.toNullableNumber(this.editModel.franquiaGestao),
|
||||||
|
locacaoAp: this.toNullableNumber(this.editModel.locacaoAp),
|
||||||
|
valorContratoLine: this.toNullableNumber(this.editModel.valorContratoLine),
|
||||||
|
desconto: this.toNullableNumber(this.editModel.desconto),
|
||||||
|
lucro: this.toNullableNumber(this.editModel.lucro),
|
||||||
|
tipoDeChip: (this.editModel.tipoDeChip ?? '').toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.http.put(`${this.apiBase}/${editingId}`, payload).subscribe({
|
||||||
next: async () => {
|
next: async () => {
|
||||||
|
try {
|
||||||
|
if (shouldUploadAttachments) {
|
||||||
|
await this.uploadAparelhoAnexos(editingId);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.editSaving = false;
|
||||||
|
await this.showToast('Registro salvo, mas falhou o upload dos anexos.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.editSaving = false;
|
this.editSaving = false;
|
||||||
|
|
||||||
// fecha e limpa overlay SEMPRE
|
// fecha e limpa overlay SEMPRE
|
||||||
this.closeAllModals();
|
this.closeAllModals();
|
||||||
|
|
||||||
await this.showToast('Registro atualizado!');
|
await this.showToast(shouldUploadAttachments ? 'Registro e anexos atualizados!' : 'Registro atualizado!');
|
||||||
|
|
||||||
if (this.isGroupMode && this.expandedGroup) {
|
if (this.isGroupMode && this.expandedGroup) {
|
||||||
const term = (this.searchTerm ?? '').trim();
|
const term = (this.searchTerm ?? '').trim();
|
||||||
|
|
@ -1976,6 +2029,115 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAparelhoNotaFiscalSelected(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement | null;
|
||||||
|
this.aparelhoNotaFiscalFile = input?.files && input.files.length > 0 ? input.files[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAparelhoReciboSelected(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement | null;
|
||||||
|
this.aparelhoReciboFile = input?.files && input.files.length > 0 ? input.files[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadAparelhoAnexo(tipo: 'nota-fiscal' | 'recibo', lineId?: string | null) {
|
||||||
|
const targetLineId = (lineId ?? this.detailData?.id ?? this.editingId ?? '').toString().trim();
|
||||||
|
if (!targetLineId) {
|
||||||
|
await this.showToast('Linha inválida para download do anexo.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.http.get(`${this.apiBase}/${targetLineId}/aparelho/anexos/${tipo}`, {
|
||||||
|
observe: 'response',
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const blob = response.body;
|
||||||
|
if (!blob) {
|
||||||
|
await this.showToast('Arquivo de anexo não encontrado.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const disposition = response.headers.get('content-disposition');
|
||||||
|
const contentType = (response.headers.get('content-type') || blob.type || '').toLowerCase();
|
||||||
|
const fallbackName = tipo === 'nota-fiscal' ? 'nota-fiscal' : 'recibo';
|
||||||
|
const fileName = this.extractAttachmentFileName(disposition, fallbackName, contentType);
|
||||||
|
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const anchor = document.createElement('a');
|
||||||
|
anchor.href = url;
|
||||||
|
anchor.download = fileName;
|
||||||
|
document.body.appendChild(anchor);
|
||||||
|
anchor.click();
|
||||||
|
anchor.remove();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
} catch {
|
||||||
|
await this.showToast('Não foi possível baixar o anexo.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadAparelhoAnexos(lineId: string): Promise<void> {
|
||||||
|
const hasAnyFile = !!this.aparelhoNotaFiscalFile || !!this.aparelhoReciboFile;
|
||||||
|
if (!hasAnyFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
if (this.aparelhoNotaFiscalFile) {
|
||||||
|
formData.append('notaFiscal', this.aparelhoNotaFiscalFile, this.aparelhoNotaFiscalFile.name);
|
||||||
|
}
|
||||||
|
if (this.aparelhoReciboFile) {
|
||||||
|
formData.append('recibo', this.aparelhoReciboFile, this.aparelhoReciboFile.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
await firstValueFrom(this.http.post(`${this.apiBase}/${lineId}/aparelho/anexos`, formData));
|
||||||
|
this.aparelhoNotaFiscalFile = null;
|
||||||
|
this.aparelhoReciboFile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractAttachmentFileName(
|
||||||
|
contentDisposition: string | null,
|
||||||
|
fallbackBaseName: string,
|
||||||
|
contentType: string
|
||||||
|
): string {
|
||||||
|
const raw = (contentDisposition ?? '').trim();
|
||||||
|
const inferredExtension = this.extensionFromContentType(contentType) ?? '.pdf';
|
||||||
|
|
||||||
|
if (!raw) {
|
||||||
|
return `${fallbackBaseName}${inferredExtension}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const utf8Match = raw.match(/filename\*=UTF-8''([^;]+)/i);
|
||||||
|
if (utf8Match?.[1]) {
|
||||||
|
return this.ensureFileNameExtension(decodeURIComponent(utf8Match[1]), inferredExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
const simpleMatch = raw.match(/filename="?([^";]+)"?/i);
|
||||||
|
if (simpleMatch?.[1]) {
|
||||||
|
return this.ensureFileNameExtension(simpleMatch[1], inferredExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${fallbackBaseName}${inferredExtension}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private extensionFromContentType(contentType: string): string | null {
|
||||||
|
if (!contentType) return null;
|
||||||
|
if (contentType.includes('application/pdf')) return '.pdf';
|
||||||
|
if (contentType.includes('image/png')) return '.png';
|
||||||
|
if (contentType.includes('image/jpeg') || contentType.includes('image/jpg')) return '.jpg';
|
||||||
|
if (contentType.includes('image/webp')) return '.webp';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureFileNameExtension(fileName: string, fallbackExtension: string): string {
|
||||||
|
const normalized = (fileName ?? '').trim();
|
||||||
|
if (!normalized) return `anexo${fallbackExtension}`;
|
||||||
|
const hasExtension = /\.[A-Za-z0-9]{2,6}$/.test(normalized);
|
||||||
|
return hasExtension ? normalized : `${normalized}${fallbackExtension}`;
|
||||||
|
}
|
||||||
|
|
||||||
async onRemover(r: LineRow, fromGroup = false) {
|
async onRemover(r: LineRow, fromGroup = false) {
|
||||||
if (!this.isSysAdmin) {
|
if (!this.isSysAdmin) {
|
||||||
await this.showToast('Apenas sysadmin pode remover linhas.');
|
await this.showToast('Apenas sysadmin pode remover linhas.');
|
||||||
|
|
@ -3430,6 +3592,15 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
||||||
return {
|
return {
|
||||||
...d,
|
...d,
|
||||||
item: d.item ?? 0,
|
item: d.item ?? 0,
|
||||||
|
centroDeCustos: d.centroDeCustos ?? '',
|
||||||
|
setorId: d.setorId ?? null,
|
||||||
|
setorNome: d.setorNome ?? '',
|
||||||
|
aparelhoId: d.aparelhoId ?? null,
|
||||||
|
aparelhoNome: d.aparelhoNome ?? '',
|
||||||
|
aparelhoCor: d.aparelhoCor ?? '',
|
||||||
|
aparelhoImei: d.aparelhoImei ?? '',
|
||||||
|
aparelhoNotaFiscalTemArquivo: !!d.aparelhoNotaFiscalTemArquivo,
|
||||||
|
aparelhoReciboTemArquivo: !!d.aparelhoReciboTemArquivo,
|
||||||
dataBloqueio: this.isoToDateInput(d.dataBloqueio),
|
dataBloqueio: this.isoToDateInput(d.dataBloqueio),
|
||||||
dataEntregaOpera: this.isoToDateInput(d.dataEntregaOpera),
|
dataEntregaOpera: this.isoToDateInput(d.dataEntregaOpera),
|
||||||
dataEntregaCliente: this.isoToDateInput(d.dataEntregaCliente),
|
dataEntregaCliente: this.isoToDateInput(d.dataEntregaCliente),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ export interface MobileLineList {
|
||||||
chip: string | null;
|
chip: string | null;
|
||||||
cliente: string | null;
|
cliente: string | null;
|
||||||
usuario: string | null;
|
usuario: string | null;
|
||||||
|
centroDeCustos?: string | null;
|
||||||
|
setorNome?: string | null;
|
||||||
|
aparelhoNome?: string | null;
|
||||||
|
aparelhoCor?: string | null;
|
||||||
planoContrato: string | null;
|
planoContrato: string | null;
|
||||||
status: string | null;
|
status: string | null;
|
||||||
skil: string | null;
|
skil: string | null;
|
||||||
|
|
@ -26,6 +30,12 @@ export interface MobileLineList {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MobileLineDetail extends MobileLineList {
|
export interface MobileLineDetail extends MobileLineList {
|
||||||
|
setorId?: string | null;
|
||||||
|
aparelhoId?: string | null;
|
||||||
|
aparelhoImei?: string | null;
|
||||||
|
aparelhoNotaFiscalTemArquivo?: boolean;
|
||||||
|
aparelhoReciboTemArquivo?: boolean;
|
||||||
|
|
||||||
franquiaVivo?: number | null;
|
franquiaVivo?: number | null;
|
||||||
valorPlanoVivo?: number | null;
|
valorPlanoVivo?: number | null;
|
||||||
gestaoVozDados?: number | null;
|
gestaoVozDados?: number | null;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue