# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import math from operator import attrgetter from decimal import Decimal from sql import Table from sql.aggregate import Sum from datetime import date, timedelta, datetime from trytond.pool import Pool, PoolMeta from trytond.transaction import Transaction from trytond.model import fields from trytond.modules.dash.dash import DashAppBase from .process_pay import ( get_pay, get_response_pay, process_response, get_dict_response_pay) class Sale(metaclass=PoolMeta): __name__ = 'sale.sale' @classmethod def dash_faster_process(cls, records): for rec in records: cls.faster_process({'id': rec.id}) @classmethod def dash_quote(cls, args, ctx=None): # Deprecation warning use create sale instead print("Deprecation Warning: use method create_sale instead") cls.create_sale(cls, args, ctx=None) @classmethod def send_order(cls, args, ctx=None): Product = Pool().get('product.product') if args['id'] <= 0: res = cls.create_sale(args, ctx) return res else: records = cls.browse([args['id']]) lines_tuple = args.get('lines') action, lines = lines_tuple if action == 'create' and len(lines) > 0: for line in lines: product = Product(line['product']) cls._add_values(product.template, line) cls.write(records, args) res = { 'record': {'id': args['id']}, 'msg': 'Pedido Actualizado!!!', 'type': 'success', } return res @classmethod def _add_values(cls, template, line): line['status_order'] = 'requested' line['unit'] = template.default_uom.id line['description'] = template.name taxes = list(template.account_category.customer_taxes_used) taxes_ids = [t.id for t in taxes] line['taxes'] = [('add', taxes_ids)] @classmethod def create_sale(cls, args, ctx=None): Shop = Pool().get('sale.shop') Product = Pool().get('product.product') Party = Pool().get('party.party') User = Pool().get('res.user') ctx = Transaction().context if ctx.get('shop'): shop = Shop(ctx['shop']) if args.get('shop'): shop = Shop(args['shop']) else: user_id = ctx.get('user') user = User(user_id) shop = user.shop lines = args['lines'] nested_values = None for line in lines: if line and line[0] == 'create': nested_values = line[1] if nested_values: lines = nested_values for v in lines: if v.get('id'): del v['id'] if v.get('amount'): del v['amount'] if v.get('unit_price_w_tax'): del v['unit_price_w_tax'] if v.get('total_amount'): del v['total_amount'] v['type'] = 'line' v['status_order'] = 'requested' v['unit_price'] = round(Decimal(v['unit_price']), 4) product = Product(v['product']) if v.get('discount') and v['discount'] != '': v['discount'] = Decimal(v['discount']) / 100 elif v.get('discount'): del v['discount'] template = product.template cls._add_values(template, v) try: price_list = args['price_list']['id'] except Exception: price_list = args.get('price_list', None) try: # for kid party, = Party.browse([args['party']['id']]) except Exception: # for fastkid party, = Party.browse([args['party']]) if args.get('shipment_address'): try: # for kid shipment_address_id = args.get('shipment_address')['id'] except Exception: # for fastkid shipment_address_id = args.get('shipment_address') else: shipment_address_id = party.addresses[0].id agent_id = None if args.get('agent'): try: # option kid agent_id = args['agent']['id'] except Exception: # option fastkid agent_id = args['agent'] shipment_date = None if args.get('shipment_date'): shipment_date = args['shipment_date'] description = args.get('description', '') comment = args.get('comment', '') today = date.today() to_create = { 'shop': shop.id, 'invoice_type': 'P', 'company': shop.company.id, 'party': party.id, 'sale_date': today, 'shipment_date': shipment_date, 'shipment_address': shipment_address_id, 'invoice_address': shipment_address_id, 'agent': agent_id, 'price_list': price_list, 'payment_term': shop.payment_term.id, 'state': 'draft', 'description': description, 'comment': comment, 'lines': [('create', lines)], } if args.get('consumer'): to_create['consumer'] = args['consumer'] if args.get('salesman'): to_create['salesman'] = args['salesman'] if args.get('table_assigned'): to_create['table_assigned'] = args['table_assigned'] sale, = cls.create([to_create]) if hasattr(sale, 'order_status'): cls.write([sale], {'order_status': 'requested'}) # cls.write([sale], {'state': 'quotation'}) cls.set_number([sale]) sale.save() record = args.copy() record.update({ 'id': sale.id, 'state': sale.state, 'number': sale.number, 'total_amount': sale.total_amount, }) res = { 'record': record, 'msg': f'Pedido No. {sale.number}', 'type': 'success', } return res @classmethod def get_pay_from_card(cls, args): sale, = cls.browse([args['id']]) fields = ['total_amount', 'number', 'sale_taxes', 'shop'] total_amount, number, sale_taxes, shop = attrgetter(*fields)(sale) terminal = shop.pos_terminal dic_values = { 'Operacion': '0', 'Valor': int(total_amount), 'Propina': 0, 'IVA': 0, 'Factura': number, 'Base Devolución IVA': 0, 'Código del Cajero': 0, 'Impuesto al Consumo': 0, 'Monto Base IVA': 0, 'Monto Base Impuesto al Consumo': 0, 'Recibo': number } for t in sale_taxes: if t == '01': dic_values['IVA'] = int(t['value_tax']) dic_values['Monto Base IVA'] = int(t['base']) elif t == '04': dic_values['Impuesto al Consumo'] = int(t['value_tax']) dic_values['Monto Base Impuesto al Consumo'] = int(t['base']) data = ','.join(map(str, dic_values.values())) # data = "0,15630,1200,2500,1234567,10630,987653,800,14000,1630,12345" # terminal = "AAC08581" response = get_pay(data, terminal) response_pay = process_response(response) print(response_pay, 'this is response pay') if response_pay == 'OK': res = { 'status': 'ok', 'terminal': terminal } else: res = { 'status': 'error' } return res @classmethod def get_response_pay_from_card(cls, args): # response_process_pay = None terminal = args['terminal'] response = get_response_pay(terminal) response_process_pay = process_response(response) if response_process_pay: data_pay = get_dict_response_pay(response_process_pay) result = { 'pay': data_pay, 'status': 'ok', 'msg': 'Pago exitoso' } else: result = { 'pay': None, 'status': 'error', 'msg': 'error al procesar pago' } return result @classmethod def process_pay_sale(cls, args): pool = Pool() Device = pool.get('sale.device') StatementLine = pool.get('account.statement.line') Date = pool.get('ir.date') payment = args['pay'] sale_id = args['sale_id'] sale, = cls.browse([sale_id]) if sale.residual_amount == sale.total_amount: device, = Device.search([ ('shop', '=', sale.shop.id), ]) sale.sale_device = device payment_means_code = '48' if payment['tipo_cuenta'] == 'CR' else '49' journal_id = None journal_name = None for journal in device.journals: if journal.payment_means_code == payment_means_code: journal_id = journal.id journal_name = journal.rec_name break statement_open_id = cls.is_statement_open( journal_id, sale.sale_device.id) if not statement_open_id: return { 'status': 'error', 'message': f'Pago no se puede procesar; No se encontró un estado de cuenta abierto para el diario {journal_name}' } to_create = { 'sale': sale_id, 'date': Date.today(), 'statement': statement_open_id, 'amount': sale.total_amount, 'party': sale.party.id, 'account': sale.party.account_receivable.id, 'description': sale.invoice_number or '', 'number': payment['consecutivo_transaccion'] } line, = StatementLine.create([to_create]) line.create_move() cls.wizard_generate_invoice([sale]) barcode = sale.number data_order = cls.get_order2print({'sale_id': sale_id, 'repeat': True}) data_invoice = cls.get_data({'sale_id': sale_id, 'type_doc': 'invoice'}) data_invoice['barcode'] = barcode for d in data_order[0].values(): d['barcode'] = barcode ctx = sale.get_printing_context({'device_id': sale.sale_device.id}) # shop = sale.shop # ctx = { # 'company': sale.company.party.name, # 'sale_device': sale.sale_device.name, # 'shop': shop.name, # 'street': shop.address.street, # 'user': "app.user", # 'city': shop.address.city_code.name, # 'zip': "00000", # 'phone': sale.company.party.phone, # 'id_number': sale.company.party.id_number, # 'tax_regime': "NA", # } return { 'status': 'ok', 'data_order': data_order[0], 'data_invoice': data_invoice, 'ctx': ctx, } @classmethod def dash_get_reverse_sale_price_taxed(cls, args): pool = Pool() Tax = pool.get('account.tax') Product = pool.get('product.product') product = Product(args['product']) sale_price_taxed = args['sale_price_taxed'] taxes = product.customer_taxes_used unit_price = Tax.reverse_compute(Decimal(sale_price_taxed), taxes) return {'unit_price': unit_price} @classmethod def dash_get_line(cls, args, ctx): if not args.get('product'): return {} Product = Pool().get('product.product') PriceListLine = Pool().get('product.price_list.line') product_id = args['product']['id'] product = Product(product_id) context = { 'company': ctx['company'], 'currency': ctx['currency'], } price_list_id = None price_list = args.get('price_list', None) if price_list: price_list_id = price_list['id'] context['price_list'] = price_list_id with Transaction().set_context(context): unit_price = product.list_price if price_list_id: price_lines = PriceListLine.search([ ('price_list', '=', price_list_id), ('product', '=', product_id), ]) if price_lines: price_line = price_lines[0] unit_price = float(unit_price) unit_price = Decimal(eval(price_line.formula)) # percent_commission = price_line.price_list.percent_commission # ADD TAXES # taxes_ids = [t.id for t in product.customer_taxes_used] # res = cls.get_price_with_tax([line], ['amount_w_tax', 'unit_price_w_tax']) res = { 'unit_price_w_tax': math.ceil(product.sale_price_taxed), # 'amount_w_tax': math.ceil(res['amount_w_tax'][None]), # 'taxes': [[('add'), taxes_ids]], 'unit_price': math.ceil(unit_price), 'unit': product.template.default_uom.id, 'type': 'line', } # if percent_commission: # res['commission_amount'] = round( # (unit_price * quantity * percent_commission), 0 # ) return res @classmethod def dash_create_order_call(cls, args): Shop = Pool().get('sale.shop') Product = Pool().get('product.product') Party = Pool().get('party.party') OrderStatusTime = Pool().get('sale.order_status.time') Configuration = Pool().get('sale.configuration') config = Configuration(1) User = Pool().get('res.user') ctx = Transaction().context user = User(ctx.get('user')) shop = Shop(args['shop']) attribs_del = ['id', 'amount', 'unit_price_w_tax', 'total_amount'] lines = args['lines'] nested_values = None for line in lines: if line and line[0] == 'create': nested_values = line[1] if nested_values: lines = nested_values for v in lines: keys = v.keys() for k in attribs_del: if k in keys: del v[k] v['type'] = 'line' product = Product(v['product']) v['unit_price'] = Decimal(str(v['unit_price'])) v['base_price'] = Decimal(str(v['base_price'])) if v.get('discount'): v['discount'] = Decimal(str(v['discount'])) taxes = list(product.account_category.customer_taxes_used) taxes_ids = [t.id for t in taxes] v['taxes'] = [('add', taxes_ids)] price_list = args.get('price_list', None) party = Party(args['party']) if args.get('shipment_address'): shipment_address_id = args.get('shipment_address') else: shipment_address_id = party.addresses[0].id agent_id = None if args.get('agent'): agent_id = args['agent'] shipment_date = None if args.get('shipment_date'): shipment_date = args['shipment_date'] delivery = args.get('delivery_amount', 0) if args.get('delivery_invoice') and delivery and int(delivery) > 0: product = config.delivery_product lines.append({ 'product': product.id, 'quantity': 1, 'type': 'line', 'description': product.name, 'unit': product.default_uom.id, 'unit_price': Decimal(delivery)}) delivery = 0 description = args.get('description', '') comment = args.get('comment', '') date_ = datetime.now() - timedelta(hours=5) today = date(date_.year, date_.month, date_.day) to_create = { 'consumer': args['consumer'], 'source': args['source'], 'kind': args['kind'], 'delivery_amount': delivery, 'shop': shop.id, 'invoice_type': args.get('invoice_type'), 'position': args.get('position', ''), 'payment_method': args['payment_method'], 'warehouse': shop.warehouse.id, 'salesman': user.employee, 'company': shop.company.id, 'party': party.id, 'sale_date': today, 'shipment_date': shipment_date, 'shipment_address': shipment_address_id, 'invoice_address': shipment_address_id, 'agent': agent_id, 'price_list': price_list if price_list else shop.price_list.id, 'payment_term': shop.payment_term.id, 'state': 'draft', 'order_status': 'requested', 'description': description, 'comment': comment, 'lines': [('create', lines)], } if args.get('consumer'): to_create['consumer'] = args['consumer'] order_status_time = OrderStatusTime.create([{ 'requested': datetime.now()}]) to_create['order_status_time'] = order_status_time[0].id print(to_create, 'validate to create') sale, = cls.create([to_create]) OrderStatusTime.write(order_status_time, {'sale': sale.id}) cls.set_number([sale]) record = args.copy() record.update({ 'id': sale.id, 'state': sale.state, 'number': sale.number, 'total_amount': sale.total_amount, }) res = { 'record': record, 'msg': f'Venta creada {sale.number}', 'type': 'success', 'status': 'ok', 'open_modal': True, } return res @classmethod def _set_line(cls, val, context): del val['id'] Product = Pool().get('product.product') val['type'] = 'line' product = Product(val['product']) with Transaction().set_context(context): unit_price = product.list_price unit_price = unit_price.quantize(Decimal(str(10.0 ** -4))) val['unit_price'] = unit_price val['base_price'] = unit_price val['unit'] = product.template.default_uom.id val['description'] = product.name taxes = list(product.account_category.customer_taxes_used) taxes_ids = [t.id for t in taxes] val['taxes'] = [('add', taxes_ids)] return val @classmethod def command(cls, args): Shop = Pool().get('sale.shop') ShopTable = Pool().get('sale.shop.table') User = Pool().get('res.user') context = Transaction().context user_id = context.get('user') user = User(user_id) action = 'create' if args['id'] > 0: action = 'edit' table = None if args.get('table_assigned'): try: table_id = args.get('table_assigned')['id'] except: table_id = args.get('table_assigned') table = ShopTable(table_id) if action == 'create': if context.get('shop'): shop = Shop(context['shop']) else: shop = user.shop context['price_list'] = shop.price_list for v in args['lines']: cls._set_line(v, context) party = shop.party today = date.today() to_create = { 'shop': shop.id, 'party': party.id, 'invoice_type': 'P', 'table_assigned': table.id, 'shipment_address': party.addresses[0].id, 'invoice_address': party.addresses[0].id, 'company': shop.company.id, 'sale_date': today, 'shipment_date': today, 'kind': 'to_table', 'price_list': shop.price_list, 'payment_term': shop.payment_term.id, 'state': 'draft', 'salesman': user.employee.id if user.employee else None, 'order_status': 'commanded', 'sale_device': user.sale_device.id, 'lines': [('create', args['lines'])], } if args.get('description'): to_create['description'] = args.get('description') try: sale, = cls.create([to_create]) sale.set_number([sale]) ShopTable.write([table], { 'state': 'occupied', 'sale': sale.id, }) return {'status': 'success', 'record': {'id': sale.id}, 'msg': "Orden Comandada exitosamente!"} except Exception as e: print(e, 'error') return {'status': 'error'} else: sale = cls(args['id']) to_add = [] to_write = {} if sale.table_assigned.id != table.id: to_write['table_assigned'] = table.id ShopTable.write([sale.table_assigned], { 'state': 'available', 'sale': None, }) ShopTable.write([table], { 'state': 'occupied', 'sale': sale.id, }) for v in args['lines']: line_id = v.get('id') if line_id < 0: context['price_list'] = sale.shop.price_list to_add.append(cls._set_line(v, context)) try: if to_add: to_write['lines'] = [('create', to_add)] cls.write([sale], to_write) return {'status': 'success', 'record': {'id': sale.id}, 'msg': "Orden Comandada exitosamente!"} except Exception as e: return {'status': 'error', 'record': {'id': sale.id}, 'msg': "Orden no Comandada"} print(e, 'error') @classmethod def dash_get_amount_w_tax(cls, args): Product = Pool().get('product.product') product = Product(args['product']) return product.template.compute_list_price_w_tax( Decimal(args['list_price'])) @classmethod def _get_sales_report(cls, dates, currency_id, in_thousands=False): invoice = Table('account_invoice') period = dates.get('period', None) if period: start_date = period.start_date end_date = period.end_date else: start_date = dates.get('start_date') end_date = dates.get('end_date') cursor = Transaction().connection.cursor() select = invoice.select(Sum(invoice.untaxed_amount_cache), limit=1) select.where = ( invoice.type == 'out') & ( invoice.invoice_date >= start_date) & ( invoice.currency == currency_id) & ( invoice.invoice_date <= end_date) & ( invoice.state.in_(['validated', 'posted', 'paid']) ) cursor.execute(*select) values = cursor.fetchone() value = 0 if values and values[0]: value = int(values[0]) if in_thousands: value = int(value / 1000) return value @classmethod def report_sales_month(cls, args, ctx=None): # Dash Report pool = Pool() Period = pool.get('account.period') Currency = pool.get('currency.currency') res = {} today = date.today() _date = today moment = args.get('moment', None) if moment == 'previous': _date = today - timedelta(days=30) periods = Period.search([ ('start_date', '<=', _date), ('end_date', '>=', _date), ('type', '=', 'standard') ]) if not periods: return res period = periods[0] selector_periods = Period.search([ ('fiscalyear', '=', period.fiscalyear.id), ('type', '=', 'standard') ]) selector = {p.id: p.name for p in selector_periods} currency_id = Transaction().context.get('currency') if args.get('currency'): currency_id = ctx.get('currency') currency = Currency(currency_id) dates = {'period': period} value = cls._get_sales_report(dates, currency_id, in_thousands=True) month_name = _date.strftime("%b %Y") res = { 'value': value, 'selector': selector, 'header_meta': month_name, 'desc': 'In thousands', 'desc_meta': currency.code, } return res @classmethod def report_sales_by_month(cls, args, ctx=None): # Dash Report pool = Pool() Fiscalyear = pool.get('account.fiscalyear') Currency = pool.get('currency.currency') today = date.today() moment = args.get('moment', None) _date = today if moment == 'previous': _date = today - timedelta(days=365) fiscalyear, = Fiscalyear.search([ ('start_date', '<=', _date), ('end_date', '>=', _date), ], limit=1) if not fiscalyear: return {} periods = [p for p in fiscalyear.periods if p.type == 'standard'] currency_id = Transaction().context.get('currency') if args.get('currency'): currency_id = args.get('currency') currency = Currency(currency_id) values = [] labels = [] for p in periods: month = p.start_date.strftime("%b") labels.append(month) dates = {'period': p} val = cls._get_sales_report(dates, currency_id, True) if not val: val = 0 values.append(val) res = { 'values': values, 'labels': labels, 'header_meta': fiscalyear.name, 'desc': 'In thousands', 'desc_meta': currency.code, } return res @classmethod def report_sales_year(cls, args, ctx=None): # Dash Report pool = Pool() Fiscalyear = pool.get('account.fiscalyear') Currency = pool.get('currency.currency') today = date.today() moment = args.get('moment', None) _date = today if moment == 'previous': _date = today - timedelta(days=365) fiscalyear, = Fiscalyear.search([ ('start_date', '<=', _date), ('end_date', '>=', _date), ], limit=1) if not fiscalyear: return {} selector_fy = Fiscalyear.search([ ('id', '=', fiscalyear.id), ]) selector = {p.id: p.name for p in selector_fy} periods = [p for p in fiscalyear.periods if p.type == 'standard'] currency_id = Transaction().context.get('currency') if args.get('currency'): currency_id = args.get('currency') currency = Currency(currency_id) values = [] labels = [] for p in periods: labels.append(p.name) dates = {'period': p} val = cls._get_sales_report(dates, currency_id, True) if val: values.append(val) res = { 'value': sum(values), 'header_meta': fiscalyear.name, 'desc': 'In thousands', 'desc_meta': currency.code, } return res @classmethod def report_sales_day(cls, args, ctx=None): # Dash Report pool = Pool() Currency = pool.get('currency.currency') currency_id = Transaction().context.get('currency') if args.get('currency'): currency_id = args.get('currency') currency = Currency(currency_id) today = date.today() _date = today moment = args.get('moment', None) if moment == 'previous': _date = today - timedelta(days=1) dates = { 'start_date': _date, 'end_date': _date } values = cls._get_sales_report(dates, currency_id, True) res = { 'value': values, 'header_meta': str(_date), 'desc': 'In thousands', 'desc_meta': currency.code, } return res @classmethod def report_fulfillment_goal_month(cls, args, ctx=None): # Dash Report pool = Pool() Currency = pool.get('currency.currency') Period = pool.get('account.period') GoalPeriod = pool.get('goal.period') Goal = pool.get('goal') currency_id = Transaction().context.get('currency') if args.get('currency'): currency_id = args.get('currency') currency = Currency(currency_id) today = date.today() _date = today moment = args.get('moment', None) if moment == 'previous': _date = today - timedelta(days=31) dates = { 'start_date': _date, 'end_date': _date } period, = Period.search([ ('start_date', '<=', _date), ('end_date', '>=', _date), ]) month = period.start_date.strftime("%b") dates = {'period': period} goals = GoalPeriod.search([ ('goal.kind', '=', 'sales'), ('period', '=', period.id), ], limit=1) goal = goals[0] if goals else {} sales = cls._get_sales_report(dates, currency_id, True) fulfillment = 0 value = 0 if goal: value = goal.total_amount / 1000 if sales: fulfillment = round((sales * 100) / value, 2) print(sales, goal.total_amount, fulfillment) month = _date.strftime("%b %Y") res = { 'value': value, 'args': {'legend': f'{fulfillment}%'}, 'header_meta': month, 'desc': 'In thousands', 'desc_meta': currency.code, } return res @classmethod def report_fulfillment_goal_year(cls, args, ctx=None): pool = Pool() Fiscalyear = pool.get('account.fiscalyear') Goal = pool.get('goal') today = date.today() _date = today moment = args.get('moment', None) if moment == 'previous': _date = today - timedelta(days=365) fiscalyear, = Fiscalyear.search([ ('start_date', '<=', _date), ('end_date', '>=', _date), ], limit=1) if not fiscalyear: return {} periods = [p for p in fiscalyear.periods if p.type == 'standard'] currency_id = Transaction().context.get('currency') if args.get('currency'): currency_id = args.get('currency') # currency = Currency(currency_id) values = [] labels = [] for p in periods: labels.append(p.name) dates = {'period': p} val = cls._get_sales_report(dates, currency_id, True) if val: values.append(val) sum_values = sum(values) goals = Goal.search([ ('fiscalyear', '=', fiscalyear.id), ('kind', '=', 'sales'), ], limit=1) if not goals: goal_rate = 0 missing = 0 else: goal = goals[0].total_amount / 1000 missing = goal - sum_values goal_rate = int((sum_values / goal) * 100) res = { 'labels': ["Sales", "Missing"], 'values': [sum(values), missing], 'center_label': str(goal_rate) + '%', 'header_meta': fiscalyear.name, } return res class AppDelivery(DashAppBase): 'App Delivery' __name__ = 'dash.app.delivery' company = fields.Many2One('company.company', 'Company', required=True) @classmethod def __setup__(cls): super(AppDelivery, cls).__setup__() @staticmethod def default_company(): return Transaction().context.get('company') or None class AppTakeOrder(DashAppBase): 'App Take Order' __name__ = 'dash.app.take_order' class AppOrderViewer(DashAppBase): 'App Order Viewer' __name__ = 'dash.app.order_viewer' @staticmethod def default_company(): return Transaction().context.get('company') or None class AppSaleOrder(DashAppBase): 'App Sale Order' __name__ = 'dash.app.sale_order' allow_discount = fields.Boolean('Allow Discount') allow_manual_pricing = fields.Boolean('Allow Manual Pricing', help='Allow manual pricing to user') class AppSelfServiceSale(DashAppBase): 'App Self Service Sale' __name__ = 'dash.app.self_service_sale' def validate_app(self, args): return {'status': 'ok'} class AppSaleCallCenter(DashAppBase): 'App Sale Call Center' __name__ = 'dash.app.sale_call_center' class AppSaleTurn(DashAppBase): 'App Sale Turn' __name__ = 'dash.app.sale_turn' class AppOrderNotification(DashAppBase): 'App Order Notification' __name__ = 'dash.app.order_notification'