import { sumBy, find, get, has, sum } from 'lodash'; import { Entity } from './entity'; import { ItemVenda } from './itemVenda'; import { Pagamento, Modalidade } from './pagamento'; import { genId } from '../utils/id-generator'; import { Produto, Grupo } from './produto'; import { Composto } from './composto'; import { Venda } from './venda'; import { Total } from './total'; import { Operador } from './operador'; import { Ticket } from './ticket'; import { Pessoa } from './pessoa'; import { v4 }from 'uuid'; import { KeysOfType, FunctionPropertyNames, DeepWriteable } from '../utils/util-types' import { Entrega } from './entrega'; import { ValorIngredienteAdicional } from './ingredientesAdicionais'; export class Nota extends Entity { public itens: Array; public terminal: any; public data_emissao: string; public data_movimento: string; public venda: Venda; public codigo: string; public numero_entrega: string; protected estorno?: boolean; entrega: Entrega = new Entrega(); total: Total; public reimpressoes: number; public crtEmpresa: string; public finalizada: boolean; public numeracao_automatica: boolean; public modelo: '65' | '55'; public natureza_operacao: 'venda' | 'compra' | 'lancamento_outra_operacao' | 'ajusta_estoque'; public operacao: 'saida' | 'entrada'; public chave_acesso: any; public status: any; public status_sefaz: any; public mensagem_sefaz: any; public ambiente_sefaz: any; public pessoa: Pessoa | undefined; public finalidade: string; public serie: string; public sem_taxa: boolean; public _id_conta: string; constructor(json?: any, generateId?: boolean) { super(json, generateId); json = json || {}; this.finalidade = json.finalidade || '1'; this.serie = get(this.venda, 'caixa.terminal.serie') || '1'; this.venda = new Venda(json.venda); this.codigo = json.codigo || genId(8); this.numero_entrega = json.numero_entrega || (Math.random() * 1000).toFixed(); this.data_emissao = json.data_emissao || new Date().toISOString(); this.data_movimento = json.data_movimento || new Date().toISOString(); this.crtEmpresa = json.crtEmpresa; this.itens = (json.itens || []).map(i => this.createItem(Object.assign({ crtEmpresa: this.crtEmpresa }, i || {}))); this.total = new Total(json.total); this.reimpressoes = json.reimpressoes || 0; this.estorno = json.estorno || false; this.finalizada = json.finalizada || false; this.numeracao_automatica = json.numeracao_automatica === false ? false : true; this.modelo = json.modelo; this.terminal = json.terminal || ''; this.natureza_operacao = json.natureza_operacao || 'venda'; this.operacao = json.operacao || 'saida'; this.chave_acesso = json.chave_acesso; this.status = json.status; this.status_sefaz = json.status_sefaz; this.mensagem_sefaz = json.mensagem_sefaz; this.ambiente_sefaz = json.ambiente_sefaz; this.pessoa = json.pessoa && new Pessoa(json.pessoa); this.sem_taxa = json.sem_taxa === true ? true : false; this._id_conta = json._id_conta; this.totalize(); } protected createItem(json?: any): ItemVenda { return new ItemVenda(json); } public totalize(): Total { if (!this.itens || !this.itens.length) return this.total.zerarTotal(); const itens = this.itens.filter(item => (!item.estorno)); this.total = Total.totalize(itens, this['sem_taxa'], this.entrega); super.update(); return this.total; } public addProdutoCombo(produto: Produto, itemsCombo: Array, quantidades? :Array , operador?: Operador ): Array { const precos : Array = itemsCombo.map((it,index) => { const qnt = quantidades && quantidades.length > 0 && quantidades[index] || 1; return it.preco * qnt; }); const vendedor = operador || null; const totalSemDesconto = sum(precos); let desconto: number = totalSemDesconto - produto.preco if(desconto < 0) desconto = 0 const _id_combo: string = v4(); let descontoAplicado = 0; return itemsCombo.map((itemCombo: Produto, index: number, arr: Array) => { const ehUltimo: boolean = arr.length - 1 === index const qnt = quantidades && quantidades.length > 0 && quantidades[index] || 1; const descontoItem = (itemCombo.preco * qnt / totalSemDesconto) * desconto; //desconto rateado por itens const item: ItemVenda = this.addItem(itemCombo, qnt, vendedor)[0] item._id_combo = _id_combo; // identificador desse combo item.combo = { _id: produto._id, descricao: produto.descricao } item.desconto = item.valor_final > 0.01 ? parseFloat(descontoItem.toFixed(2)) > 0 ? parseFloat(descontoItem.toFixed(2)) : 0 : 0; descontoAplicado += item.desconto; const sobra = desconto - descontoAplicado; if (ehUltimo && sobra > 0) item.desconto += sobra; const itemIndex: number = this.itens.findIndex(elm => elm._entry === item._entry); return this.changeItem(item, itemIndex); }) } public addCombo(produtos: Array, singleItem: boolean, operador?: Operador): Array { const _id_combo: string = v4() const qtdCadaItem: number = singleItem ? parseFloat((1 / produtos.length).toFixed(2)) : 1 const vendedor = operador || null; const sobra = (arr: Array): number => 1 - parseFloat(( 1 / arr.length ).toFixed(2)) * arr.length return produtos .map((produto: Produto, index: number, arr: Array) => { const ehUltimo: boolean = arr.length - 1 === index const quantidade: number = qtdCadaItem + ( ehUltimo ? sobra(arr) : 0 ) const item: ItemVenda = this.addItem(produto, quantidade , vendedor)[0] item._id_combo = _id_combo item.combo = { _id: _id_combo, descricao: produto.grupo && produto.grupo instanceof Grupo && (produto.grupo as Grupo).nome, fracionado : true } const itemIndex: number = this.itens.findIndex(elm => elm._entry === item._entry) return this.changeItem(item, itemIndex) }) } /** * Adição de itens na nota. * Este método verifica se o produto contém composição * e se essas geram tickets unitários, assim como, * verifica se as mesmas serão incluídas na nota * @param {Produto} produto Produto para ser adicionado * @param {Operador} operador Operador que realizou a transação */ public addItem(produto: Produto, quantidade?: number, operador?: Operador): Array { const itens = []; const itemPrincipal = this.createItem({ produto, quantidade, vendedor: operador, crtEmpresa: this.crtEmpresa, impressora: get(produto.grupo, 'impressora') || '' }); itemPrincipal['_changed'] = true; itemPrincipal.recalc(false, this.crtEmpresa); if (!produto.incluir_composicao_nota) itens.push(itemPrincipal); if (produto.composicao_ativa) (produto.composicoes || []).map((composto: Composto) => { if (produto.incluir_composicao_nota) { const itemComposicao = this.createItem({ produto: composto.produto, quantidade: composto.quantidade, vendedor: operador, crtEmpresa: this.crtEmpresa }); itemComposicao.recalc(false, this.crtEmpresa); itens.push(itemComposicao) } }) this.itens = this.itens.concat(itens) this.totalize(); return itens; } public incItem(itemVenda: ItemVenda, quantidade?: number) { quantidade = quantidade || 1; itemVenda.setQuantidade(itemVenda.quantidade + quantidade, this.crtEmpresa); return this.changeItem(itemVenda, this.itens.findIndex(i => i._entry === itemVenda._entry)) } public decItem(itemVenda: ItemVenda, quantidade?: number) { quantidade = quantidade || 1; itemVenda.setQuantidade(itemVenda.quantidade - quantidade, this.crtEmpresa); return this.changeItem(itemVenda, this.itens.findIndex(i => i._entry === itemVenda._entry)) } public calcularImposto() { this.itens.forEach((item: ItemVenda) => item.calcularImposto(this.crtEmpresa)); return this.totalize(); } public changeItem(itemVenda: ItemVenda, index: number): ItemVenda { itemVenda.recalc(false, this.crtEmpresa) this.itens.splice(index, 1, itemVenda) this.totalize() return itemVenda } public changeIngredientesAdicionais(ingredientes : Array, itemVenda: ItemVenda){ const oldItem = this.itens[this.itens.findIndex(elm => elm._entry == itemVenda._entry)]; //remove os ingredientes antigos this.itens = this.itens.filter(elm => !oldItem.ingredientes_adicionais.map(it => it._entry).includes(elm._entry) ); itemVenda.ingredientes_adicionais = []; //adiciona os novos ingredientes.forEach( (ing) => { const item: ItemVenda = this.addItem(ing,1,itemVenda.vendedor)[0]; item._adicional = true; itemVenda.ingredientes_adicionais.push({ _id : item._id, descricao : item.descricao, preco : item.valor_final, _entry : item._entry }); const itemIndex: number = this.itens.findIndex(elm => elm._entry == item._entry) return this.changeItem(item, itemIndex) }) //altera o item principal return this.changeItem(itemVenda, this.itens.findIndex(elm => elm._entry === itemVenda._entry)) } public removeIngredienteAdicional(ingrediente : ValorIngredienteAdicional, itemVenda: ItemVenda){ //remove o ingrediente da venda this.itens = this.itens.filter(elm => elm._entry !== ingrediente._entry); //remove o ingrediente do item.ingredientes_adicionais itemVenda.ingredientes_adicionais = itemVenda.ingredientes_adicionais.filter(el => el._entry !== ingrediente._entry); return this.changeItem(itemVenda, this.itens.findIndex(elm => elm._entry === itemVenda._entry)) } public removeItem(itemVenda: ItemVenda, remove_siblings?: true): ItemVenda { if (itemVenda._adicional){ const parent = this.itens.find(elm => elm.ingredientes_adicionais.map(it => it._entry).includes(itemVenda._entry)) this.removeIngredienteAdicional(parent.ingredientes_adicionais.find(el => el._entry == itemVenda._entry),parent) } else{ this.itens.splice(this.itens.findIndex(i => i._entry === itemVenda._entry), 1); } if (remove_siblings) this.itens = this.itens.filter(elm => (!elm._id_combo || elm._id_combo !== itemVenda._id_combo) && ( !itemVenda.ingredientes_adicionais || !itemVenda.ingredientes_adicionais.map(it => it._entry).includes(elm._entry) ) ) this.totalize(); return itemVenda; } public ratear(p: KeysOfType>, 'extra'>, number>, valor: number, replace?: boolean): Total { if (!this.itens.length) return const sobra = valor - this.itens .filter(i => !i.estorno) .reduce((total: number, item_venda: ItemVenda) => { const valor_proporcional: number = (item_venda.valor_final / this.total.vprod) * valor const rateio: number = parseFloat(valor_proporcional.toFixed(2)) // Caso o parâmetro replace seja verdadeiro, substituirá o valor existente item_venda[p] = ( replace ? 0.0 : item_venda[p] ) + Number(rateio) total += rateio return total }, // Caso o valor seja negativo, o reduce começa por ele, se não começa com zero valor >= 0 ? 0.0 : valor ) this.itens[0][p] += sobra return this.totalize() } public valorTotal(): number { return this.total.vnf; } public totalQtdItens(): number { return this.total.vqtd; } public valorPago(): number { return this.venda.pagamentos.reduce((total, pagamento) => total += pagamento.valor, 0); } public troco(): number { return this.valorPago() - this.valorTotal(); } public fechar(): Promise { if (this.valorTotal() > sumBy(this.venda.pagamentos, 'valor')) throw Error('Falta pagamento'); this.finalizada = true; super.update(); return Promise.resolve(); } /** * Função para ratearDesconto * @deprecated Função depreciada em favor da ratear * @param desconto valor do desconto */ public ratearDesconto(desconto: number): Total { const total = this.total.vprod; if (total === 0 || desconto >= total) { return; } let itens = this.itens .filter(item => !item.estorno) .map((item: ItemVenda) => { item.desconto = item.desconto || 0; let increment = (item.valor_total / total) * desconto; increment = Math.round(increment * 100) / 100; item.desconto += increment; item.recalc(false, this.crtEmpresa); return item; }); const correcao = (this.total.vdesc + desconto) - sumBy(itens, 'desconto'); itens[0].desconto += correcao; return this.totalize() } public setPagamentos(pagamentos: Pagamento[]) { this.venda.pagamentos = []; pagamentos.map(pagamento => this.addPagamento(pagamento)); super.update(); } public addPagamento(pagamento: Pagamento) { let descricao = typeof pagamento.modalidade === 'string' && pagamento.modalidade || get(pagamento, 'modalidade.descricao'); if (descricao == 'DESCONTO') { this.ratearDesconto(pagamento.valor); } this.venda.pagamentos.push(pagamento); super.update(); } public verificarTroco() { const total = sumBy(this.venda.pagamentos, 'valor'); let trocoTotal = parseFloat((total - this.valorTotal()).toFixed(2)); if (trocoTotal > 0) { this.venda.pagamentos.forEach((pagamento, index) => { if(trocoTotal >= pagamento.valor) { pagamento.troco = pagamento.valor - 0.01; trocoTotal -= pagamento.valor - 0.01; } else { pagamento.troco = trocoTotal; trocoTotal = 0; } }) } } public estornar() { this.estorno = true; super.update(); } public isEstornada() { return this.estorno; } public contabilizarReimpressao() { this.reimpressoes++; } }