line-gestao-frontend/src/app/pages/chips-controle-recebidos/chips-controle-recebidos.ts

928 lines
30 KiB
TypeScript

import { Component, Inject, PLATFORM_ID, OnInit, OnDestroy } from '@angular/core';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { ChipsControleService, ChipVirgemListDto, ControleRecebidoListDto, SortDir, UpdateChipVirgemRequest, UpdateControleRecebidoRequest, CreateChipVirgemRequest, CreateControleRecebidoRequest } from '../../services/chips-controle.service';
import { CustomSelectComponent } from '../../components/custom-select/custom-select';
import { ChipsControleModalsComponent } from '../../components/page-modals/chips-controle-modals/chips-controle-modals';
import { AuthService } from '../../services/auth.service';
import { TableExportService } from '../../services/table-export.service';
import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation';
import {
buildPageNumbers,
clampPage,
computePageEnd,
computePageStart,
computeTotalPages
} from '../../utils/pagination.util';
import { firstValueFrom } from 'rxjs';
// Interface para o Agrupamento
interface ChipGroup {
observacao: string;
total: number;
items: ChipVirgemListDto[];
}
interface ControleGroup {
conteudo: string;
total: number;
items: ControleRecebidoListDto[];
}
interface ChipVirgemCreateModel {
id: string;
item: number | null;
numeroDoChip: string | null;
observacoes: string | null;
}
type ChipsSortKey = 'item' | 'numeroDoChip' | 'observacoes';
type ControleSortKey =
| 'ano' | 'item' | 'notaFiscal' | 'chip' | 'serial' | 'conteudoDaNf' | 'numeroDaLinha'
| 'valorUnit' | 'valorDaNf' | 'dataDaNf' | 'dataDoRecebimento' | 'quantidade' | 'isResumo';
@Component({
selector: 'app-chips-controle-recebidos',
standalone: true,
imports: [CommonModule, FormsModule, CustomSelectComponent, ChipsControleModalsComponent],
templateUrl: './chips-controle-recebidos.html',
styleUrls: ['./chips-controle-recebidos.scss']
})
export class ChipsControleRecebidos implements OnInit, OnDestroy {
readonly vm = this;
activeTab: 'chips' | 'controle' = 'chips';
// --- Chips ---
chipsRows: ChipVirgemListDto[] = [];
chipsGroups: ChipGroup[] = [];
pagedChipsGroups: ChipGroup[] = [];
expandedGroupObservacao: string | null = null;
chipsLoading = false;
chipsSearch = '';
chipsPage = 1;
chipsPageSize = 10;
chipsTotal = 0;
chipsSortBy: ChipsSortKey = 'item';
chipsSortDir: SortDir = 'asc';
private chipsSearchTimer: any = null;
// --- Controle ---
controleRows: ControleRecebidoListDto[] = [];
controleGroups: ControleGroup[] = [];
pagedControleGroups: ControleGroup[] = [];
expandedControleConteudo: string | null = null;
controleLoading = false;
controleSearch = '';
controlePage = 1;
controlePageSize = 10;
controleTotal = 0;
controleSortBy: ControleSortKey = 'ano';
controleSortDir: SortDir = 'desc';
controleAno: number | '' = '';
controleResumo: '' | 'true' | 'false' = '';
private controleSearchTimer: any = null;
// --- Opções ---
pageSizeOptions = [10, 20, 50, 100];
anoOptions = [
{ label: 'Todos', value: '' },
{ label: '2022', value: 2022 },
{ label: '2023', value: 2023 },
{ label: '2024', value: 2024 },
{ label: '2025', value: 2025 }
];
toastOpen = false;
toastMessage = '';
toastType: 'success' | 'danger' = 'success';
exporting = false;
private toastTimer: any = null;
chipDetailOpen = false;
chipDetailLoading = false;
chipDetailData: ChipVirgemListDto | null = null;
chipCreateOpen = false;
chipCreateSaving = false;
chipCreateModel: ChipVirgemCreateModel | null = null;
chipEditOpen = false;
chipEditSaving = false;
chipEditModel: ChipVirgemListDto | null = null;
chipEditingId: string | null = null;
chipDeleteOpen = false;
chipDeleteTarget: ChipVirgemListDto | null = null;
controleDetailOpen = false;
controleDetailLoading = false;
controleDetailData: ControleRecebidoListDto | null = null;
controleCreateOpen = false;
controleCreateSaving = false;
controleCreateModel: ControleRecebidoListDto | null = null;
controleCreateDataNf = '';
controleCreateRecebimento = '';
controleEditOpen = false;
controleEditSaving = false;
controleEditModel: ControleRecebidoListDto | null = null;
controleEditDataNf = '';
controleEditRecebimento = '';
controleEditingId: string | null = null;
controleDeleteOpen = false;
controleDeleteTarget: ControleRecebidoListDto | null = null;
isSysAdmin = false;
constructor(
@Inject(PLATFORM_ID) private platformId: object,
private service: ChipsControleService,
private http: HttpClient,
private authService: AuthService,
private tableExportService: TableExportService
) {}
ngOnInit(): void {
if (!isPlatformBrowser(this.platformId)) return;
this.isSysAdmin = this.authService.hasRole('sysadmin');
this.fetchChips();
this.fetchControle();
}
ngOnDestroy(): void {
if (this.chipsSearchTimer) clearTimeout(this.chipsSearchTimer);
if (this.controleSearchTimer) clearTimeout(this.controleSearchTimer);
if (this.toastTimer) clearTimeout(this.toastTimer);
}
setTab(tab: 'chips' | 'controle') {
this.activeTab = tab;
if (tab === 'chips') {
this.expandedGroupObservacao = null;
this.applyChipsPagination();
this.closeControleDetail();
} else {
this.expandedControleConteudo = null;
this.applyControlePagination();
this.closeChipDetail();
}
}
// =====================
// Chips Virgens
// =====================
fetchChips() {
this.chipsLoading = true;
this.service.getChipsVirgens({
search: this.chipsSearch,
page: 1,
pageSize: 5000,
sortBy: this.chipsSortBy,
sortDir: this.chipsSortDir
}).subscribe({
next: (res) => {
const items = (res as any)?.items ?? [];
this.chipsRows = items.map((x: any, idx: number) => this.normalizeChip(x, idx));
this.buildChipsGroups();
this.chipsTotal = this.chipsGroups.length;
this.applyChipsPagination();
this.chipsLoading = false;
},
error: () => {
this.chipsLoading = false;
this.showToast('Erro ao carregar Chips Virgens.', 'danger');
}
});
}
private buildChipsGroups() {
const groupsMap = new Map<string, ChipVirgemListDto[]>();
this.chipsRows.forEach(row => {
const key = row.observacoes && row.observacoes.trim() !== ''
? row.observacoes.trim()
: '(Sem Observações)';
if (!groupsMap.has(key)) groupsMap.set(key, []);
groupsMap.get(key)?.push(row);
});
this.chipsGroups = [];
groupsMap.forEach((items, key) => {
this.chipsGroups.push({ observacao: key, total: items.length, items });
});
this.chipsGroups.sort((a, b) => a.observacao.localeCompare(b.observacao));
this.expandedGroupObservacao = null;
}
private applyChipsPagination() {
const start = (this.chipsPage - 1) * this.chipsPageSize;
const end = start + this.chipsPageSize;
this.pagedChipsGroups = this.chipsGroups.slice(start, end);
if (this.expandedGroupObservacao && !this.pagedChipsGroups.some(g => g.observacao === this.expandedGroupObservacao)) {
this.expandedGroupObservacao = null;
}
}
toggleGroup(obs: string) {
this.expandedGroupObservacao = this.expandedGroupObservacao === obs ? null : obs;
}
openChipDetail(row: ChipVirgemListDto) {
if (!row?.id) return;
this.chipDetailOpen = true;
this.chipDetailLoading = true;
this.chipDetailData = null;
this.service.getChipVirgemById(row.id).subscribe({
next: (data) => {
this.chipDetailData = data ?? row;
this.chipDetailLoading = false;
},
error: () => {
this.chipDetailLoading = false;
this.chipDetailData = row;
}
});
}
openChipCreate() {
if (!this.isSysAdmin) return;
this.chipCreateModel = {
id: '',
item: null,
numeroDoChip: '',
observacoes: ''
};
this.chipCreateOpen = true;
this.chipCreateSaving = false;
}
closeChipCreate() {
this.chipCreateOpen = false;
this.chipCreateSaving = false;
this.chipCreateModel = null;
}
saveChipCreate() {
if (!this.chipCreateModel) return;
this.chipCreateSaving = true;
const payload: CreateChipVirgemRequest = {
item: this.toNullableNumber(this.chipCreateModel.item),
numeroDoChip: this.chipCreateModel.numeroDoChip,
observacoes: this.chipCreateModel.observacoes
};
this.service.createChipVirgem(payload).subscribe({
next: () => {
this.chipCreateSaving = false;
this.closeChipCreate();
this.fetchChips();
this.showToast('Chip criado com sucesso!', 'success');
},
error: () => {
this.chipCreateSaving = false;
this.showToast('Erro ao criar chip.', 'danger');
}
});
}
openChipEdit(row: ChipVirgemListDto) {
if (!this.isSysAdmin) return;
this.service.getChipVirgemById(row.id).subscribe({
next: (data) => {
this.chipEditingId = data.id;
this.chipEditModel = { ...data };
this.chipEditOpen = true;
},
error: () => this.showToast('Erro ao abrir edição.', 'danger')
});
}
closeChipEdit() {
this.chipEditOpen = false;
this.chipEditSaving = false;
this.chipEditModel = null;
this.chipEditingId = null;
}
saveChipEdit() {
if (!this.chipEditModel || !this.chipEditingId) return;
this.chipEditSaving = true;
const payload: UpdateChipVirgemRequest = {
item: this.toNullableNumber(this.chipEditModel.item),
numeroDoChip: this.chipEditModel.numeroDoChip,
observacoes: this.chipEditModel.observacoes
};
this.service.updateChipVirgem(this.chipEditingId, payload).subscribe({
next: () => {
this.chipEditSaving = false;
this.closeChipEdit();
this.fetchChips();
this.showToast('Chip atualizado!', 'success');
},
error: () => {
this.chipEditSaving = false;
this.showToast('Erro ao salvar.', 'danger');
}
});
}
openChipDelete(row: ChipVirgemListDto) {
if (!this.isSysAdmin) return;
this.chipDeleteTarget = row;
this.chipDeleteOpen = true;
}
cancelChipDelete() {
this.chipDeleteOpen = false;
this.chipDeleteTarget = null;
}
async confirmChipDelete() {
if (!this.chipDeleteTarget) return;
if (!(await confirmDeletionWithTyping('este chip virgem'))) return;
const id = this.chipDeleteTarget.id;
this.service.removeChipVirgem(id).subscribe({
next: () => {
this.chipDeleteOpen = false;
this.chipDeleteTarget = null;
this.fetchChips();
this.showToast('Chip removido.', 'success');
},
error: () => {
this.chipDeleteOpen = false;
this.chipDeleteTarget = null;
this.showToast('Erro ao remover.', 'danger');
}
});
}
closeChipDetail() {
this.chipDetailOpen = false;
this.chipDetailLoading = false;
this.chipDetailData = null;
}
onChipsSearch() {
if (this.chipsSearchTimer) clearTimeout(this.chipsSearchTimer);
this.chipsSearchTimer = setTimeout(() => {
this.chipsPage = 1;
this.fetchChips();
}, 300);
}
clearChipsSearch() {
this.chipsSearch = '';
this.chipsPage = 1;
this.fetchChips();
}
onChipsPageSizeChange() {
this.chipsPage = 1;
this.applyChipsPagination();
}
// =====================
// Controle Recebidos
// =====================
fetchControle() {
this.controleLoading = true;
this.service.getControleRecebidos({
search: this.controleSearch,
page: 1,
pageSize: 5000,
sortBy: this.controleSortBy,
sortDir: this.controleSortDir,
ano: this.controleAno,
isResumo: this.controleResumo
}).subscribe({
next: (res) => {
const items = (res as any)?.items ?? [];
this.controleRows = items.map((x: any, idx: number) => this.normalizeControle(x, idx));
this.buildControleGroups();
this.controleTotal = this.controleGroups.length;
this.applyControlePagination();
this.controleLoading = false;
},
error: () => {
this.controleLoading = false;
this.showToast('Erro ao carregar Controle.', 'danger');
}
});
}
onControleSearch() {
if (this.controleSearchTimer) clearTimeout(this.controleSearchTimer);
this.controleSearchTimer = setTimeout(() => {
this.controlePage = 1;
this.fetchControle();
}, 300);
}
clearControleSearch() {
this.controleSearch = '';
this.controlePage = 1;
this.fetchControle();
}
async onExport(): Promise<void> {
if (this.exporting) return;
this.exporting = true;
try {
if (this.activeTab === 'chips') {
const baseRows = [...(this.chipsRows ?? [])].sort((a, b) => (a.item ?? 0) - (b.item ?? 0));
const rows = await this.fetchDetailedChipRowsForExport(baseRows);
if (!rows.length) {
this.showToast('Nenhum registro encontrado para exportar.', 'danger');
return;
}
const timestamp = this.tableExportService.buildTimestamp();
await this.tableExportService.exportAsXlsx<ChipVirgemListDto>({
fileName: `chips_virgens_${timestamp}`,
sheetName: 'ChipsVirgens',
rows,
columns: [
{ header: 'ID', value: (row) => row.id ?? '' },
{ header: 'Item', type: 'number', value: (row) => this.toNullableNumber(row.item) ?? 0 },
{ header: 'Numero do Chip', value: (row) => row.numeroDoChip ?? '' },
{ header: 'Observacoes', value: (row) => row.observacoes ?? '' },
{ header: 'Criado Em', type: 'datetime', value: (row) => row.createdAt ?? '' },
{ header: 'Atualizado Em', type: 'datetime', value: (row) => row.updatedAt ?? '' },
],
});
this.showToast(`Planilha exportada com ${rows.length} registro(s).`, 'success');
return;
}
const baseRows = [...(this.controleRows ?? [])].sort((a, b) => {
const byAno = (this.toNullableNumber(a.ano) ?? 0) - (this.toNullableNumber(b.ano) ?? 0);
if (byAno !== 0) return byAno;
return (this.toNullableNumber(a.item) ?? 0) - (this.toNullableNumber(b.item) ?? 0);
});
const rows = await this.fetchDetailedControleRowsForExport(baseRows);
if (!rows.length) {
this.showToast('Nenhum registro encontrado para exportar.', 'danger');
return;
}
const timestamp = this.tableExportService.buildTimestamp();
await this.tableExportService.exportAsXlsx<ControleRecebidoListDto>({
fileName: `controle_recebidos_${timestamp}`,
sheetName: 'ControleRecebidos',
rows,
columns: [
{ header: 'ID', value: (row) => row.id ?? '' },
{ header: 'Ano', type: 'number', value: (row) => this.toNullableNumber(row.ano) ?? 0 },
{ header: 'Item', type: 'number', value: (row) => this.toNullableNumber(row.item) ?? 0 },
{ header: 'Nota Fiscal', value: (row) => row.notaFiscal ?? '' },
{ header: 'Chip', value: (row) => row.chip ?? '' },
{ header: 'Serial', value: (row) => row.serial ?? '' },
{ header: 'Conteudo da NF', value: (row) => row.conteudoDaNf ?? '' },
{ header: 'Numero da Linha', value: (row) => row.numeroDaLinha ?? '' },
{ header: 'Valor Unitario', type: 'currency', value: (row) => this.toNullableNumber(row.valorUnit) ?? 0 },
{ header: 'Valor da NF', type: 'currency', value: (row) => this.toNullableNumber(row.valorDaNf) ?? 0 },
{ header: 'Data da NF', type: 'date', value: (row) => row.dataDaNf ?? '' },
{ header: 'Data do Recebimento', type: 'date', value: (row) => row.dataDoRecebimento ?? '' },
{ header: 'Quantidade', type: 'number', value: (row) => this.toNullableNumber(row.quantidade) ?? 0 },
{ header: 'Resumo', type: 'boolean', value: (row) => !!row.isResumo },
{ header: 'Criado Em', type: 'datetime', value: (row) => row.createdAt ?? '' },
{ header: 'Atualizado Em', type: 'datetime', value: (row) => row.updatedAt ?? '' },
],
});
this.showToast(`Planilha exportada com ${rows.length} registro(s).`, 'success');
} catch {
this.showToast('Erro ao exportar planilha.', 'danger');
} finally {
this.exporting = false;
}
}
private async fetchDetailedChipRowsForExport(rows: ChipVirgemListDto[]): Promise<ChipVirgemListDto[]> {
if (!rows.length) return [];
const detailed: ChipVirgemListDto[] = [];
const chunkSize = 12;
for (let i = 0; i < rows.length; i += chunkSize) {
const chunk = rows.slice(i, i + chunkSize);
const resolved = await Promise.all(
chunk.map(async (row) => {
try {
return await firstValueFrom(this.service.getChipVirgemById(row.id));
} catch {
return row;
}
})
);
detailed.push(...resolved);
}
return detailed;
}
private async fetchDetailedControleRowsForExport(rows: ControleRecebidoListDto[]): Promise<ControleRecebidoListDto[]> {
if (!rows.length) return [];
const detailed: ControleRecebidoListDto[] = [];
const chunkSize = 12;
for (let i = 0; i < rows.length; i += chunkSize) {
const chunk = rows.slice(i, i + chunkSize);
const resolved = await Promise.all(
chunk.map(async (row) => {
try {
return await firstValueFrom(this.service.getControleRecebidoById(row.id));
} catch {
return row;
}
})
);
detailed.push(...resolved);
}
return detailed;
}
setControleSort(key: ControleSortKey) {
if (this.controleSortBy === key) {
this.controleSortDir = this.controleSortDir === 'asc' ? 'desc' : 'asc';
} else {
this.controleSortBy = key;
this.controleSortDir = 'asc';
}
this.controlePage = 1;
this.fetchControle();
}
onControlePageSizeChange() {
this.controlePage = 1;
this.applyControlePagination();
}
onControleAnoChange() {
this.controlePage = 1;
this.fetchControle();
}
setControleResumo(val: '' | 'true' | 'false') {
this.controleResumo = val;
this.controlePage = 1;
this.fetchControle();
}
private buildControleGroups() {
const groupsMap = new Map<string, ControleRecebidoListDto[]>();
this.controleRows.forEach(row => {
const key = row.conteudoDaNf && row.conteudoDaNf.trim() !== ''
? row.conteudoDaNf.trim()
: '(Sem Conteúdo)';
if (!groupsMap.has(key)) groupsMap.set(key, []);
groupsMap.get(key)?.push(row);
});
this.controleGroups = [];
groupsMap.forEach((items, key) => {
this.controleGroups.push({ conteudo: key, total: items.length, items });
});
this.controleGroups.sort((a, b) => a.conteudo.localeCompare(b.conteudo));
this.expandedControleConteudo = null;
}
private applyControlePagination() {
const start = (this.controlePage - 1) * this.controlePageSize;
const end = start + this.controlePageSize;
this.pagedControleGroups = this.controleGroups.slice(start, end);
if (this.expandedControleConteudo && !this.pagedControleGroups.some(g => g.conteudo === this.expandedControleConteudo)) {
this.expandedControleConteudo = null;
}
}
toggleControleGroup(conteudo: string) {
this.expandedControleConteudo = this.expandedControleConteudo === conteudo ? null : conteudo;
}
openControleDetail(row: ControleRecebidoListDto) {
if (!row?.id) return;
this.controleDetailOpen = true;
this.controleDetailLoading = true;
this.controleDetailData = null;
this.service.getControleRecebidoById(row.id).subscribe({
next: (data) => {
this.controleDetailData = data ?? row;
this.controleDetailLoading = false;
},
error: () => {
this.controleDetailLoading = false;
this.controleDetailData = row;
}
});
}
openControleCreate() {
if (!this.isSysAdmin) return;
this.controleCreateModel = {
id: '',
ano: new Date().getFullYear(),
item: null,
notaFiscal: '',
chip: '',
serial: '',
conteudoDaNf: '',
numeroDaLinha: '',
valorUnit: null,
valorDaNf: null,
dataDaNf: null,
dataDoRecebimento: null,
quantidade: null,
isResumo: false
} as ControleRecebidoListDto;
this.controleCreateDataNf = '';
this.controleCreateRecebimento = '';
this.controleCreateOpen = true;
this.controleCreateSaving = false;
}
closeControleCreate() {
this.controleCreateOpen = false;
this.controleCreateSaving = false;
this.controleCreateModel = null;
this.controleCreateDataNf = '';
this.controleCreateRecebimento = '';
}
onControleCreateValueChange() {
if (!this.controleCreateModel) return;
this.recalculateControleTotals(this.controleCreateModel);
}
onControleEditValueChange() {
if (!this.controleEditModel) return;
this.recalculateControleTotals(this.controleEditModel);
}
onControleCreateDateChange() {
if (!this.controleCreateModel) return;
if (!this.controleCreateModel.ano && this.controleCreateDataNf) {
const year = new Date(this.controleCreateDataNf).getFullYear();
if (Number.isFinite(year)) this.controleCreateModel.ano = year;
}
}
onControleEditDateChange() {
if (!this.controleEditModel) return;
if (!this.controleEditModel.ano && this.controleEditDataNf) {
const year = new Date(this.controleEditDataNf).getFullYear();
if (Number.isFinite(year)) this.controleEditModel.ano = year;
}
}
private recalculateControleTotals(model: ControleRecebidoListDto) {
const quantidade = this.toNullableNumber(model.quantidade);
const valorUnit = this.toNullableNumber(model.valorUnit);
const valorDaNf = this.toNullableNumber(model.valorDaNf);
if (quantidade != null && valorUnit != null && (valorDaNf == null || valorDaNf === 0)) {
model.valorDaNf = Number((valorUnit * quantidade).toFixed(2));
} else if (quantidade != null && valorDaNf != null && (valorUnit == null || valorUnit === 0)) {
model.valorUnit = Number((valorDaNf / quantidade).toFixed(2));
}
}
saveControleCreate() {
if (!this.controleCreateModel) return;
this.recalculateControleTotals(this.controleCreateModel);
this.controleCreateSaving = true;
const payload: CreateControleRecebidoRequest = {
ano: this.toNullableNumber(this.controleCreateModel.ano),
item: this.toNullableNumber(this.controleCreateModel.item),
notaFiscal: this.controleCreateModel.notaFiscal,
chip: this.controleCreateModel.chip,
serial: this.controleCreateModel.serial,
conteudoDaNf: this.controleCreateModel.conteudoDaNf,
numeroDaLinha: this.controleCreateModel.numeroDaLinha,
valorUnit: this.toNullableNumber(this.controleCreateModel.valorUnit),
valorDaNf: this.toNullableNumber(this.controleCreateModel.valorDaNf),
dataDaNf: this.dateInputToIso(this.controleCreateDataNf),
dataDoRecebimento: this.dateInputToIso(this.controleCreateRecebimento),
quantidade: this.toNullableNumber(this.controleCreateModel.quantidade),
isResumo: this.controleCreateModel.isResumo ?? false
};
this.service.createControleRecebido(payload).subscribe({
next: () => {
this.controleCreateSaving = false;
this.closeControleCreate();
this.fetchControle();
this.showToast('Recebimento criado com sucesso!', 'success');
},
error: () => {
this.controleCreateSaving = false;
this.showToast('Erro ao criar recebimento.', 'danger');
}
});
}
openControleEdit(row: ControleRecebidoListDto) {
if (!this.isSysAdmin) return;
this.service.getControleRecebidoById(row.id).subscribe({
next: (data) => {
this.controleEditingId = data.id;
this.controleEditModel = { ...data };
this.controleEditDataNf = this.toDateInput(data.dataDaNf);
this.controleEditRecebimento = this.toDateInput(data.dataDoRecebimento);
this.controleEditOpen = true;
},
error: () => this.showToast('Erro ao abrir edição.', 'danger')
});
}
closeControleEdit() {
this.controleEditOpen = false;
this.controleEditSaving = false;
this.controleEditModel = null;
this.controleEditDataNf = '';
this.controleEditRecebimento = '';
this.controleEditingId = null;
}
saveControleEdit() {
if (!this.controleEditModel || !this.controleEditingId) return;
this.recalculateControleTotals(this.controleEditModel);
this.controleEditSaving = true;
const payload: UpdateControleRecebidoRequest = {
ano: this.toNullableNumber(this.controleEditModel.ano),
item: this.toNullableNumber(this.controleEditModel.item),
notaFiscal: this.controleEditModel.notaFiscal,
chip: this.controleEditModel.chip,
serial: this.controleEditModel.serial,
conteudoDaNf: this.controleEditModel.conteudoDaNf,
numeroDaLinha: this.controleEditModel.numeroDaLinha,
valorUnit: this.toNullableNumber(this.controleEditModel.valorUnit),
valorDaNf: this.toNullableNumber(this.controleEditModel.valorDaNf),
dataDaNf: this.dateInputToIso(this.controleEditDataNf),
dataDoRecebimento: this.dateInputToIso(this.controleEditRecebimento),
quantidade: this.toNullableNumber(this.controleEditModel.quantidade),
isResumo: this.controleEditModel.isResumo ?? false
};
this.service.updateControleRecebido(this.controleEditingId, payload).subscribe({
next: () => {
this.controleEditSaving = false;
this.closeControleEdit();
this.fetchControle();
this.showToast('Registro atualizado!', 'success');
},
error: () => {
this.controleEditSaving = false;
this.showToast('Erro ao salvar.', 'danger');
}
});
}
openControleDelete(row: ControleRecebidoListDto) {
if (!this.isSysAdmin) return;
this.controleDeleteTarget = row;
this.controleDeleteOpen = true;
}
cancelControleDelete() {
this.controleDeleteOpen = false;
this.controleDeleteTarget = null;
}
async confirmControleDelete() {
if (!this.controleDeleteTarget) return;
if (!(await confirmDeletionWithTyping('este registro de controle recebido'))) return;
const id = this.controleDeleteTarget.id;
this.service.removeControleRecebido(id).subscribe({
next: () => {
this.controleDeleteOpen = false;
this.controleDeleteTarget = null;
this.fetchControle();
this.showToast('Registro removido.', 'success');
},
error: () => {
this.controleDeleteOpen = false;
this.controleDeleteTarget = null;
this.showToast('Erro ao remover.', 'danger');
}
});
}
private toDateInput(value: string | null): string {
if (!value) return '';
const d = new Date(value);
if (isNaN(d.getTime())) return '';
return d.toISOString().slice(0, 10);
}
private dateInputToIso(value: string): string | null {
if (!value) return null;
const d = new Date(`${value}T00:00:00`);
if (isNaN(d.getTime())) return null;
return d.toISOString();
}
private toNullableNumber(value: any): number | null {
if (value === undefined || value === null || value === '') return null;
const n = Number(value);
return Number.isNaN(n) ? null : n;
}
closeControleDetail() {
this.controleDetailOpen = false;
this.controleDetailLoading = false;
this.controleDetailData = null;
}
// =====================
// Paginação e Helpers
// =====================
get activePage() { return this.activeTab === 'chips' ? this.chipsPage : this.controlePage; }
get activeTotal() { return this.activeTab === 'chips' ? this.chipsTotal : this.controleTotal; }
get activePageSize() { return this.activeTab === 'chips' ? this.chipsPageSize : this.controlePageSize; }
get activeTotalPages() { return computeTotalPages(this.activeTotal || 0, this.activePageSize || 10); }
get activePageStart() { return computePageStart(this.activeTotal || 0, this.activePage, this.activePageSize); }
get activePageEnd() { return computePageEnd(this.activeTotal || 0, this.activePage, this.activePageSize); }
get activeLoading() { return this.activeTab === 'chips' ? this.chipsLoading : this.controleLoading; } // ✅ novo
get activePageNumbers() {
return buildPageNumbers(this.activePage, this.activeTotalPages);
}
goToPage(p: number) {
const target = clampPage(p, this.activeTotalPages);
if (this.activeTab === 'chips') {
this.chipsPage = target;
this.applyChipsPagination();
} else {
this.controlePage = target;
this.applyControlePagination();
}
}
normalizeChip(x: any, idx: number): ChipVirgemListDto {
return {
id: String(x.id || idx),
item: Number(x.item || 0),
numeroDoChip: x.numeroDoChip || x.NumeroDoChip,
observacoes: x.observacoes || x.Observacoes
};
}
normalizeControle(x: any, idx: number): ControleRecebidoListDto {
return {
id: String(x.id || idx),
ano: x.ano,
item: x.item,
notaFiscal: x.notaFiscal,
chip: x.chip,
serial: x.serial,
conteudoDaNf: x.conteudoDaNf,
numeroDaLinha: x.numeroDaLinha,
valorUnit: x.valorUnit,
valorDaNf: x.valorDaNf,
dataDaNf: x.dataDaNf,
dataDoRecebimento: x.dataDoRecebimento,
quantidade: x.quantidade,
isResumo: x.isResumo
};
}
display(val: any) { return val ? String(val) : '-'; }
formatMoney(val: any) {
if (!val) return '-';
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(val);
}
formatDate(val: any) { if (!val) return '-'; return new Date(val).toLocaleDateString('pt-BR'); }
isResumo(r: any) { return !!r.isResumo; }
getResumoItems(items: ControleRecebidoListDto[]) { return (items || []).filter(r => this.isResumo(r)); }
getDetalheItems(items: ControleRecebidoListDto[]) { return (items || []).filter(r => !this.isResumo(r)); }
trackById(idx: number, item: any) { return item.id; }
showToast(msg: string, type: 'success' | 'danger') {
this.toastMessage = msg;
this.toastType = type;
this.toastOpen = true;
setTimeout(() => this.toastOpen = false, 3000);
}
}