Compare commits
No commits in common. "5d0dc3b367087544eb97abf6d47664a132836566" and "79624205650425dda94e4c55c34e42b5d0fabd29" have entirely different histories.
5d0dc3b367
...
7962420565
|
|
@ -398,9 +398,9 @@
|
|||
<td>
|
||||
<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 primary" (click)="onEditar(r)" title="Editar"><i class="bi bi-pencil-square"></i></button>
|
||||
<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 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>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
|
@ -517,9 +517,9 @@
|
|||
<td class="text-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 primary" (click)="onEditar(r)" title="Editar"><i class="bi bi-pencil-square"></i></button>
|
||||
<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 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>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
|
@ -1693,8 +1693,8 @@
|
|||
|
||||
<div class="modal-body modern-body bg-light-gray" *ngIf="detailData; else detailLoading">
|
||||
<div class="details-dashboard">
|
||||
<ng-template #detailIdentificacaoCard>
|
||||
<div class="detail-box">
|
||||
<div class="dashboard-column">
|
||||
<div class="detail-box h-100">
|
||||
<div class="box-header justify-content-center">
|
||||
<span><i class="bi bi-person-badge me-2"></i> Identificação</span>
|
||||
</div>
|
||||
|
|
@ -1712,13 +1712,9 @@
|
|||
<span class="lbl">Usuário</span>
|
||||
<span class="val">{{ detailData.usuario || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item span-2">
|
||||
<div class="info-item span-2" *ngIf="isClientRestricted">
|
||||
<span class="lbl">Centro de Custos</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>
|
||||
<span class="val text-dark"> </span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="lbl">Item</span>
|
||||
|
|
@ -1739,69 +1735,6 @@
|
|||
</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 class="dashboard-column d-flex flex-column gap-2" *ngIf="!isClientRestricted">
|
||||
|
|
@ -1986,85 +1919,7 @@
|
|||
|
||||
<div class="modal-body modern-body bg-light-gray">
|
||||
<ng-container *ngIf="editModel; else editLoadingTpl">
|
||||
<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">
|
||||
<div class="edit-sections">
|
||||
<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">
|
||||
|
|
@ -2077,45 +1932,6 @@
|
|||
<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>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>
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -518,18 +518,12 @@
|
|||
.modal-body .box-body { overflow: visible; }
|
||||
.modal-xl-custom { width: min(1100px, 95vw); max-height: 85vh; }
|
||||
.modal-card.modal-client-detail {
|
||||
width: min(980px, 96vw);
|
||||
width: min(560px, 95vw);
|
||||
}
|
||||
.modal-card.modal-client-detail .details-dashboard {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
max-width: 940px;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (max-width: 900px) {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 520px;
|
||||
}
|
||||
margin: 0 auto;
|
||||
}
|
||||
.modal-card.modal-create { width: min(1280px, 96vw); max-height: 92vh; }
|
||||
.modal-card.modal-create.batch-mode { width: min(1560px, 99vw); }
|
||||
|
|
|
|||
|
|
@ -49,10 +49,6 @@ interface LineRow {
|
|||
chip?: string;
|
||||
cliente: string;
|
||||
usuario: string;
|
||||
centroDeCustos?: string;
|
||||
setorNome?: string;
|
||||
aparelhoNome?: string;
|
||||
aparelhoCor?: string;
|
||||
status: string;
|
||||
skil: string;
|
||||
contrato: string;
|
||||
|
|
@ -72,10 +68,6 @@ interface ApiLineList {
|
|||
chip?: string | null;
|
||||
cliente: string | null;
|
||||
usuario: string | null;
|
||||
centroDeCustos?: string | null;
|
||||
setorNome?: string | null;
|
||||
aparelhoNome?: string | null;
|
||||
aparelhoCor?: string | null;
|
||||
vencConta: string | null;
|
||||
status?: string | null;
|
||||
skil?: string | null;
|
||||
|
|
@ -97,15 +89,6 @@ interface ApiLineDetail {
|
|||
tipoDeChip?: string | null;
|
||||
cliente?: 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;
|
||||
status?: string | null;
|
||||
skil?: string | null;
|
||||
|
|
@ -382,8 +365,6 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
detailData: any = null;
|
||||
financeData: any = null;
|
||||
editModel: any = null;
|
||||
aparelhoNotaFiscalFile: File | null = null;
|
||||
aparelhoReciboFile: File | null = null;
|
||||
|
||||
private editingId: string | null = null;
|
||||
private searchTimer: any = null;
|
||||
|
|
@ -854,8 +835,6 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
this.detailData = null;
|
||||
this.financeData = null;
|
||||
this.aparelhoNotaFiscalFile = null;
|
||||
this.aparelhoReciboFile = null;
|
||||
|
||||
this.editSaving = false;
|
||||
this.createSaving = false;
|
||||
|
|
@ -1630,10 +1609,6 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
chip: x.chip ?? '',
|
||||
cliente: x.cliente ?? '',
|
||||
usuario: x.usuario ?? '',
|
||||
centroDeCustos: x.centroDeCustos ?? '',
|
||||
setorNome: x.setorNome ?? '',
|
||||
aparelhoNome: x.aparelhoNome ?? '',
|
||||
aparelhoCor: x.aparelhoCor ?? '',
|
||||
status: x.status ?? '',
|
||||
skil: x.skil ?? '',
|
||||
contrato: x.vencConta ?? ''
|
||||
|
|
@ -1866,8 +1841,6 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.editOpen = true;
|
||||
this.editSaving = false;
|
||||
this.editModel = null;
|
||||
this.aparelhoNotaFiscalFile = null;
|
||||
this.aparelhoReciboFile = null;
|
||||
this.editingId = r.id;
|
||||
|
||||
this.cdr.detectChanges();
|
||||
|
|
@ -1945,26 +1918,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (!this.editingId || !this.editModel) return;
|
||||
|
||||
this.editSaving = true;
|
||||
const editingId = this.editingId;
|
||||
const shouldUploadAttachments = !!(this.aparelhoNotaFiscalFile || this.aparelhoReciboFile);
|
||||
let payload: UpdateMobileLineRequest;
|
||||
|
||||
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 { contaEmpresa: _contaEmpresa, ...editModelPayload } = this.editModel;
|
||||
|
||||
payload = {
|
||||
const payload: UpdateMobileLineRequest = {
|
||||
...editModelPayload,
|
||||
item: this.toInt(this.editModel.item),
|
||||
dataBloqueio: this.dateInputToIso(this.editModel.dataBloqueio),
|
||||
|
|
@ -1990,26 +1948,15 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
lucro: this.toNullableNumber(this.editModel.lucro),
|
||||
tipoDeChip: (this.editModel.tipoDeChip ?? '').toString()
|
||||
};
|
||||
}
|
||||
|
||||
this.http.put(`${this.apiBase}/${editingId}`, payload).subscribe({
|
||||
this.http.put(`${this.apiBase}/${this.editingId}`, payload).subscribe({
|
||||
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;
|
||||
|
||||
// fecha e limpa overlay SEMPRE
|
||||
this.closeAllModals();
|
||||
|
||||
await this.showToast(shouldUploadAttachments ? 'Registro e anexos atualizados!' : 'Registro atualizado!');
|
||||
await this.showToast('Registro atualizado!');
|
||||
|
||||
if (this.isGroupMode && this.expandedGroup) {
|
||||
const term = (this.searchTerm ?? '').trim();
|
||||
|
|
@ -2029,115 +1976,6 @@ 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) {
|
||||
if (!this.isSysAdmin) {
|
||||
await this.showToast('Apenas sysadmin pode remover linhas.');
|
||||
|
|
@ -3592,15 +3430,6 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
return {
|
||||
...d,
|
||||
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),
|
||||
dataEntregaOpera: this.isoToDateInput(d.dataEntregaOpera),
|
||||
dataEntregaCliente: this.isoToDateInput(d.dataEntregaCliente),
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ export interface MobileLineList {
|
|||
chip: string | null;
|
||||
cliente: string | null;
|
||||
usuario: string | null;
|
||||
centroDeCustos?: string | null;
|
||||
setorNome?: string | null;
|
||||
aparelhoNome?: string | null;
|
||||
aparelhoCor?: string | null;
|
||||
planoContrato: string | null;
|
||||
status: string | null;
|
||||
skil: string | null;
|
||||
|
|
@ -30,12 +26,6 @@ export interface MobileLineList {
|
|||
}
|
||||
|
||||
export interface MobileLineDetail extends MobileLineList {
|
||||
setorId?: string | null;
|
||||
aparelhoId?: string | null;
|
||||
aparelhoImei?: string | null;
|
||||
aparelhoNotaFiscalTemArquivo?: boolean;
|
||||
aparelhoReciboTemArquivo?: boolean;
|
||||
|
||||
franquiaVivo?: number | null;
|
||||
valorPlanoVivo?: number | null;
|
||||
gestaoVozDados?: number | null;
|
||||
|
|
|
|||
Loading…
Reference in New Issue