# -*- coding: utf-8 -*- from openerp import http from openerp.http import request from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, float_round from werkzeug.wrappers import Response from werkzeug.datastructures import Headers from datetime import datetime from dateutil.relativedelta import relativedelta as rd from dateutil.parser import parse from pytz import timezone from gzip import GzipFile import simplejson as json import gzip import logging try: from cStringIO import StringIO as IO except ImportError: from StringIO import StringIO as IO LOGGER = logging.getLogger(__name__) GZIP_COMPRESSION_LEVEL = 9 ''' ___ ___ __ __ __ ___ __ __ ___ __ ___ __ __ __ __ __ |__| | | |__) / ` / \ |\ | | |__) / \ | | |__ |__) |__ / \ |__) |__) / \ /__` | | | | | \__, \__/ | \| | | \ \__/ |___ |___ |___ | \ | \__/ | \ | \__/ .__/ # Resource paths - /init: return json response gzip compressed contains values for sale operation - /create_product: create product on the fly POS and return it - /create_customer: create customer on the fly and return it - /process_sale: processing sale and return true if completed ''' class PosSales(http.Controller): ''' Make JSON response ''' def make_json_response(self, data=None, status=200): return Response(json.dumps(data), status=status, content_type='application/json') ''' Make GZIP to JSON response ''' def make_gzip_response(self, data=None, status=200): gzip_buffer = IO() with GzipFile(mode='wb', compresslevel=GZIP_COMPRESSION_LEVEL, fileobj=gzip_buffer) as gzip_file: gzip_file.write(json.dumps(data)) contents = gzip_buffer.getvalue() gzip_buffer.close() headers = Headers() headers.add('Content-Encoding', 'gzip') headers.add('Vary', 'Accept-Encoding') headers.add('Content-Length', len(contents)) return Response(contents, status=status, headers=headers, content_type='application/json') ''' ''' def make_info_log(self, log): LOGGER.info(log) ''' Get POS data ''' def get_pos_data(self): pos = request.env['eiru.pos'].search([]) return { 'imageType': pos.image_type, 'allowCurrencyExchange': pos.allow_currency_exchange, 'viewCurrencyExchanges': pos.view_currency_exchanges } ''' Get timezone ''' def get_timezone(self): return timezone(request.context['tz']) ''' Get server date ''' def get_server_datetime(self): return datetime.now(self.get_timezone()).strftime(DEFAULT_SERVER_DATETIME_FORMAT) ''' Check if module is installed ''' def is_module_installed(self, module_name): domain = [('name', '=', module_name), ('state', '=', 'installed')] module = request.env['ir.module.module'].search(domain) return len(module) != 0 ''' Get current user information ''' def get_user(self): user = request.env.user return { 'id': user.id, 'name': user.display_name, 'company': { 'id': user.company_id.id, 'name': user.company_id.display_name, 'phone': user.company_id.phone or None, 'city': user.company_id.city or None } } ''' Get currencies from configured journals ''' def get_currencies_from_journals(self): domain = [('type', 'in', ['bank', 'cash']), ('active', '=', True)] currencies = [] for journal in request.env['account.journal'].search(domain): currency = journal.currency or journal.company_id.currency_id currencies.append({ 'id': currency.id, 'name': currency.display_name, 'base': currency.base, 'symbol': currency.symbol, 'position': currency.position, 'rateSilent': currency.rate_silent, 'decimalSeparator': currency.decimal_separator, 'decimalPlaces': currency.decimal_places, 'thousandsSeparator': currency.thousands_separator }) return {c['id']:c for c in currencies}.values() ''' Get all active journals ''' def get_journals(self): domain = [('type', 'in', ['bank', 'cash']), ('active', '=', True)] has_subtype = self.is_module_installed('eiru_bank_payments_references') return [{ 'id': journal.id, 'name': journal.display_name, 'code': journal.code, 'type': journal.type, 'subtype': (has_subtype and journal.subtype) or None, 'currencyId': journal.currency.id or journal.company_id.currency_id.id } for journal in request.env['account.journal'].search(domain, order='id')] ''' Get banks ''' def get_banks(self): banks = [] if self.is_module_installed('eiru_bank_payments_references'): banks = [ { 'id': bank.id, 'name': bank.display_name } for bank in request.env['res.bank'].search([('active', '=', True)]) ] return banks ''' Get bank payment types ''' def get_bank_payment_types(self): bank_types = [] if self.is_module_installed('eiru_bank_payments_references'): domain = [('is_receipt', '=', True)] bank_types = [ { 'id': type.id, 'name': type.display_name, 'isReceipt': type.is_receipt, 'isPayment': type.is_payment, 'fieldsAllowed': map(lambda x: x.strip(), (type.fields_allowed or []).split(',')), 'subtype': type.subtype, 'defaultState': type.default_state } for type in request.env['res.bank.payments.type'].search(domain) ] return bank_types ''' Get cheque ''' def get_cheque_types(self): cheque_types = [] if self.is_module_installed('eiru_bank_payments_references'): cheque_types = [ { 'id': type.id, 'name': type.display_name, 'isBank': type.is_bank, 'isCash': type.is_cash } for type in request.env['res.bank.check.type'].search([]) ] return cheque_types ''' Get all active customers ''' def get_customers(self, image_type='small'): domain = [('customer', '=', True), ('active', '=', True)] return [ { 'id': customer.id, 'name': customer.display_name, 'image': customer.image_small if image_type == 'small' else customer.image_medium, 'ruc': customer.ruc or None, 'phone': customer.phone or None, 'mobile': customer.mobile or None, 'email': customer.email or None } for customer in request.env['res.partner'].search(domain) ] ''' Get all saleable and active products ''' def get_products(self, image_type='small'): domain = [('sale_ok', '=', True), ('list_price', '>', 0), ('active', '=', True)] return [ { 'id': product.id, 'name': product.display_name, 'ean13': product.ean13, 'defaultCode': product.default_code, 'image': product.image_small if image_type == 'small' else product.image_medium, 'listPrice': product.list_price, 'variantCount': product.product_variant_count, 'quantity': 1, 'price': product.list_price, 'minimumPrice': product.minimum_price, 'maximumPrice': product.maximum_price, 'discount': 0, 'variants': [{ 'id': variant.id, 'name': variant.display_name, 'ean13': variant.ean13, 'defaultCode': product.default_code, 'image': variant.image_small if image_type == 'small' else variant.image_medium, 'listPrice': variant.list_price, 'quantity': 1, 'price': variant.list_price, 'minimumPrice': product.minimum_price, 'maximumPrice': product.maximum_price, 'discount': 0, } for variant in product.product_variant_ids if variant.active] } for product in request.env['product.template'].search(domain) ] ''' Get all active payment terms ''' def get_payment_terms(self): domain = [('active', '=', True)] return [ { 'id': payment_term.id, 'name': payment_term.display_name, 'lines': [{ 'id': line.id, 'days': line.days, 'days2': line.days2, 'value': line.value, 'valueAmount': line.value_amount } for line in payment_term.line_ids] } for payment_term in request.env['account.payment.term'].search(domain) ] ''' Get currency from journal ''' def get_currency(self, journal_id): journal = request.env['account.journal'].browse(journal_id) return journal.default_credit_account_id.currency_id.id or journal.default_credit_account_id.company_currency_id.id ''' Check currency in pricelist and get it ''' def get_pricelist(self, currency_id): pricelist = request.env['product.pricelist'].search([('active', '=', True), ('type', '=', 'sale')]) if not True in pricelist.mapped(lambda p: p.currency_id.id == currency_id): pricelist = pricelist[0].copy() pricelist.write({ 'currency_id': currency_id }) else: pricelist = pricelist.filtered(lambda p: p.currency_id.id == currency_id) return pricelist ''' ''' def compute_cart_item_price(self, price, currency_id, partner_id=None): from_currency = request.env.user.company_id.currency_id to_currency = request.env['res.currency'].search([('id', '=', currency_id)]) return from_currency.compute(price, to_currency) ''' ''' def compute_cart_item_discount(self, price, product_id, currency_id): from_currency = request.env.user.company_id.currency_id to_currency = request.env['res.currency'].search([('id', '=', currency_id)]) product = request.env['product.product'].search([('id', '=', product_id)]) computed_price = from_currency.compute(price, to_currency) return 1.0 - (price / product.list_price) ''' Create sale order from cart items values ''' def create_sale_from_cart(self, partner_id, cart_items, date_confirm, currency_id, pricelist_id, payment_term_id=None): values = { 'partner_id': partner_id, 'order_line': [[0, False, { 'product_id': int(line.get('id')), 'product_uom_qty': float(line.get('quantity')), 'price_unit': self.compute_cart_item_price(float(line.get('price')), currency_id), 'discount': self.compute_cart_item_discount(float(line.get('price')), float(line.get('id')), currency_id) }] for line in cart_items], 'picking_policy': 'direct', 'date_confirm': date_confirm, 'currency_id': currency_id, 'pricelist_id': pricelist_id, 'payment_term': payment_term_id, 'state': 'draft', } # import web_pdb; web_pdb.set_trace() return request.env['sale.order'].create(values) ''' Confirm sale order ''' def confirm_sale_order(self, sale_order_id): sale_order = request.env['sale.order'].browse(sale_order_id) sale_order.write({ 'state': 'manual' }) return sale_order.action_button_confirm() ''' Create invoice from sale order ''' def create_invoice(self, sale_order_id, currency_id, date_today): sale_order = request.env['sale.order'].browse(sale_order_id) invoice_id = sale_order.action_invoice_create() invoice = request.env['account.invoice'].browse(invoice_id) for picking in sale_order.picking_ids: picking.force_assign() picking.action_done() date_due = parse(date_today) + rd(days=max(invoice.payment_term.line_ids.mapped(lambda x: x.days + x.days2))) invoice.write({ 'currency_id': currency_id, 'date_invoice': date_today, 'date_due': date_due.strftime(DEFAULT_SERVER_DATE_FORMAT), 'state': 'open' }) return invoice ''' Create move lines ''' def create_invoice_move_lines(self, invoice_id, paid_amount, date_today): invoice = request.env['account.invoice'].browse(invoice_id) context = dict(request.context, lang=invoice.partner_id.lang) invoice_move_lines = invoice._get_analytic_lines() decimal_precision = request.env['decimal.precision'].precision_get('Account') compute_taxes = request.env['account.invoice.tax'].compute(invoice.with_context(context)) invoice.check_tax_lines(compute_taxes) invoice._recompute_tax_amount() # import web_pdb; web_pdb.set_trace() invoice_move_lines += request.env['account.invoice.tax'].move_line_get(invoice.id) total, total_currency, invoice_move_lines = invoice.with_context(context).compute_invoice_totals(invoice.company_id.currency_id, invoice.reference, invoice_move_lines) paid_amount = float_round(paid_amount, precision_digits=decimal_precision) paid_percentage = paid_amount / float_round(total_currency, precision_digits=decimal_precision) distributed_percentage = -(paid_percentage / len(invoice.payment_term.line_ids)) diff_currency = invoice.currency_id != invoice.company_id.currency_id if diff_currency: paid_amount = invoice.currency_id.compute(paid_amount, invoice.company_id.currency_id) payment_lines = [] for line in invoice.payment_term.line_ids: date_due = (parse(date_today) + rd(days=line.days + line.days2)).strftime(DEFAULT_SERVER_DATE_FORMAT) if paid_percentage and paid_percentage < 1.0: payment_lines.append([date_today, paid_percentage]) paid_percentage = paid_amount = 0 if date_due == date_today and line.value_amount: distributed_percentage = -((payment_lines[0][1] - line.value_amount) / (len(invoice.payment_term.line_ids) - 1)) continue if line.value != 'balance': payment_lines.append([date_due, line.value_amount + distributed_percentage]) continue payment_lines.append([date_due, line.value_amount]) for payment_line in payment_lines: current_price = float_round(total * payment_line[1], precision_digits=decimal_precision) if current_price < 0.0: continue paid_amount += current_price # Compute move line price if payment_line[1]: price = current_price else: price = float_round(total - paid_amount, precision_digits=decimal_precision) or total # Compute move line amount in currency if diff_currency: amount_currency = invoice.company_id.currency_id.with_context(context).compute(price, invoice.currency_id) else: amount_currency = False # Create invoice move live value invoice_move_lines.append({ 'type': 'dest', 'name': '/', 'price': price, 'account_id': invoice.account_id.id, 'date_maturity': payment_line[0], 'amount_currency': amount_currency, 'currency_id': diff_currency and invoice.currency_id.id, 'ref': invoice.reference }) payment_lines = [] return invoice_move_lines ''' Create account move ''' def create_account_move(self, invoice_id, invoice_move_lines): invoice = request.env['account.invoice'].browse(invoice_id) accounting_partner = request.env['res.partner']._find_accounting_partner(invoice.partner_id) move_line_values = [(0, 0, invoice.line_get_convert(line, accounting_partner.id, invoice.date_invoice)) for line in invoice_move_lines] move_line_values = invoice.group_lines(invoice_move_lines, move_line_values) move_line_values = invoice.finalize_invoice_move_lines(move_line_values) ctx = dict(request.context, lang=invoice.partner_id.lang, company_id=invoice.company_id.id) period = invoice.period_id if not period: period = period.with_context(ctx).find(invoice.date_invoice)[:1] if period: for line in move_line_values: line[2]['period_id'] = period.id ctx['invoice'] = invoice ctx_nolang = ctx.copy() ctx_nolang.pop('lang', None) account_move = request.env['account.move'].with_context(ctx_nolang).create({ 'ref': invoice.reference or invoice.name, 'line_id': move_line_values, 'journal_id': invoice.journal_id.id, 'date': invoice.date_invoice, 'narration': invoice.comment, 'company_id': invoice.company_id.id, 'period_id': period.id }) invoice.with_context(ctx).write({ 'move_id': account_move.id, 'period_id': account_move.period_id.id, 'move_name': account_move.name, }) account_move.post() return account_move ''' Number to invoice ''' def number_invoice(self, invoice_id): request.env['account.invoice'].browse(invoice_id).action_number() ''' Create voucher ''' def create_account_voucher(self, account_move_id, journal_id, currency_id, paid_amount): account_move = request.env['account.move'].browse(account_move_id) account_journal = request.env['account.journal'].browse(journal_id) account_voucher = request.env['account.voucher'].create({ 'reference': account_move.name, 'type': 'receipt', 'journal_id': account_journal.id, 'company_id': account_move.company_id.id, 'pre_line': True, 'amount': paid_amount, 'period_id': account_move.period_id.id, 'date': account_move.date, 'partner_id': account_move.partner_id.id, 'account_id': account_journal.default_credit_account_id.id, 'currency_id': currency_id, 'line_cr_ids': [[0, False, { 'date_due': l.date_maturity, 'account_id': l.account_id.id, 'date_original': l.invoice.date_invoice, 'move_line_id': l.id, 'amount_original': abs(l.credit or l.debit or 0.0), 'amount_unreconciled': abs(l.amount_residual), 'amount': abs(l.debit) if account_move.date == l.date_maturity else 0.0, 'reconcile': account_move.date == l.date_maturity, 'currency_id': currency_id }] for l in account_move.line_id] }) account_voucher.action_move_line_create() return account_voucher ''' Close a invoice ''' def close_invoice(self, invoice_id): invoice = request.env['account.invoice'].browse(invoice_id) if invoice.residual == 0: invoice.write({ 'state': 'paid' }) ''' Create account bank statement lines ''' def create_bank_statement_lines(self, account_voucher_id, reference=None): account_voucher = request.env['account.voucher'].browse(account_voucher_id) return [[0, False, { 'name': account_voucher.reference, 'amount': account_voucher.amount, 'partner_id': account_voucher.partner_id.id, 'voucher_id': account_voucher.id, 'journal_id': account_voucher.journal_id.id, 'account_id': account_voucher.account_id.id, 'journal_entry_id': account_voucher.move_id.id, 'currency_id': account_voucher.currency_id.id, 'ref': 'POS/' + (reference or '') }]] ''' Create account bank statement ''' def create_bank_statement(self, account_voucher_id, account_bank_statement_lines, date_today): account_voucher = request.env['account.voucher'].browse(account_voucher_id) account_bank_statement = request.env['account.bank.statement'].search([('journal_id', '=', account_voucher.journal_id.id), ('date', '=', date_today)]) account_bank_statement_values = { 'date': date_today, 'user_id': request.env.user.id, 'journal_id': account_voucher.journal_id.id, 'period_id': account_voucher.period_id.id, 'line_ids': account_bank_statement_lines, 'state': 'open' if account_voucher.journal_id.type == 'cash' else 'draft' } if account_bank_statement: size = len(account_bank_statement) if size == 1: account_bank_statement.write(account_bank_statement_values) else: account_bank_statement[size - 1].write(account_bank_statement_values) else: account_bank_statement.create(account_bank_statement_values) return account_bank_statement ''' New purchase resource route ''' @http.route('/eiru_sales/init', auth='user', methods=['GET'], cors='*') def init_sale(self, **kw): self.make_info_log('Sending JSON response') pos_data = self.get_pos_data() return self.make_gzip_response( { 'pos': pos_data, 'date': self.get_server_datetime(), 'user': self.get_user(), 'currencies': self.get_currencies_from_journals(), 'journals': self.get_journals(), 'customers': self.get_customers(image_type=pos_data['imageType']), 'products': self.get_products(image_type=pos_data['imageType']), 'paymentTerms': self.get_payment_terms(), 'banks': self.get_banks(), 'bankPaymentTypes': self.get_bank_payment_types(), 'chequeTypes': self.get_cheque_types() } ) ''' Get products data only ''' @http.route('/eiru_sales/get_images', auth='user', methods=['GET'], cors='*') def get_images_only(self, **kw): pos_data = self.get_pos_data() image_type = str(pos_data['imageType']) return self.make_gzip_response( { 'customers': self.get_customers(image_type=image_type), 'products': self.get_products(image_type=image_type) } ) ''' Create customer and return data ''' @http.route('/eiru_sales/create_customer', type='json', auth='user', methods=['POST'], cors='*') def create_customer(self, **kw): self.make_info_log('Creating customer') customer = request.env['res.partner'].create({ 'name': kw.get('name'), 'ruc': kw.get('ruc'), 'mobile': kw.get('mobile'), 'customer': True }) return { 'id': customer.id, 'name': customer.display_name, 'image': customer.image_small, 'ruc': customer.ruc or None, 'phone': customer.phone or None, 'mobile': customer.mobile or None, 'email': customer.email or None } ''' Save settings ''' @http.route('/eiru_sales/save_settings', type='json', auth='user', methods=['POST'], cors='*') def save_settings(self, **kw): self.make_info_log('save settings') values = {} if kw.get('setting') == 'imageType': values['image_type'] = ('big', 'small')[kw.get('value', False)] if kw.get('setting') == 'allowCurrencyExchange': values['allow_currency_exchange'] = kw.get('value', False) if kw.get('setting') == 'viewCurrencyExchanges': values['view_currency_exchanges'] = kw.get('value', False) pos = request.env['eiru.pos'].search([]) pos.write(values) return self.get_pos_data() ''' Create product and return data ''' @http.route('/eiru_sales/create_product', type='json', auth='user', methods=['POST'], cors='*') def create_product(self, **kw): self.make_info_log('Creating customer') product = request.env['product.template'].create({ 'name': kw.get('name'), 'listPrice': float(kw.get('price')), 'ean13': kw.get('ean13') }) return { 'id': product.id, 'name': product.display_name, 'ean13': product.ean13, 'image': product.image_small, 'listPrice': product.list_price, 'variantCount': product.product_variant_count, 'quantity': 1, 'price': product.list_price, 'discount': 0, 'variants': [{ 'id': variant.id, 'name': variant.display_name, 'ean13': variant.ean13, 'image': variant.image_small, 'listPrice': variant.list_price, 'quantity': 1, 'price': variant.list_price, 'discount': 0, } for variant in product.product_variant_ids if variant.active] } ''' Process sale ''' @http.route('/eiru_sales/process_sale', type='json', auth='user', methods=['POST'], cors='*') def process_sale(self, **kw): self.make_info_log('Processing sale...') # Get date date_now = datetime.now(self.get_timezone()).strftime(DEFAULT_SERVER_DATE_FORMAT) self.make_info_log('[OK] Getting date') # Get currency currency_id = self.get_currency(kw.get('journalId')) self.make_info_log('[OK] Getting journal') # Get Pricelist pricelist = self.get_pricelist(currency_id) self.make_info_log('[OK] Getting product pricelist') # Create sale order sale_order = self.create_sale_from_cart(kw.get('customerId'), kw.get('items'), date_now, currency_id, pricelist.id, kw.get('paymentTermId')) self.make_info_log('[OK] Creating sale order') # Check if budget if kw.get('mode', 'sale') != 'sale': return { 'process': True } # Confirm sale self.confirm_sale_order(sale_order.id) self.make_info_log('[OK] Confirm sale order') # Create invoice invoice = self.create_invoice(sale_order.id, currency_id, date_now) self.make_info_log('[OK] Creating invoice') # Create invoice move lines invoice_move_lines = self.create_invoice_move_lines(invoice.id, float(kw.get('payment')), date_now) self.make_info_log('[OK] Creating invoice move lines') # Create account move account_move = self.create_account_move(invoice.id, invoice_move_lines) self.make_info_log('[OK] Creating account move') # Number invoice self.number_invoice(invoice.id) self.make_info_log('[OK] Number invoice') # Create account voucher account_voucher = self.create_account_voucher(account_move.id, kw.get('journalId'), currency_id, float(kw.get('payment'))) self.make_info_log('[OK] Creating account voucher') # Close invoice self.close_invoice(invoice.id) self.make_info_log('[OK] Closing invoice') # Create account bank statement lines account_bank_statement_lines = self.create_bank_statement_lines(account_voucher.id) self.make_info_log('[OK] Creating account bank statement lines') # Create bank statement self.create_bank_statement(account_voucher.id, account_bank_statement_lines, date_now) self.make_info_log('[OK] Creating account bank statement') return { 'process': True, 'name': sale_order.display_name, 'date': self.get_server_datetime() }