# -*- coding: utf-8 -*- from openerp import http from openerp.http import request 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 from StringIO import StringIO as IO import simplejson as json import gzip import logging LOGGER = logging.getLogger(__name__) DATE_FORMAT = '%Y-%m-%d' DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' 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): ''' 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(DATETIME_FORMAT) ''' Check if module is installed ''' def check_module(self, module_name): module = request.env['ir.module.module'].search([('name', '=', module_name), ('state', '=', 'installed')]) return len(module) != 0 ''' Get current user information ''' def get_user(self): user = request.env.user return { 'id': user.id, 'name': user.name, 'displayName': user.display_name, 'currency': { 'id': user.company_id.currency_id.id, 'name': user.company_id.currency_id.name, 'displayName': user.company_id.currency_id.display_name, 'symbol': user.company_id.currency_id.symbol }, 'company': { 'id': user.company_id.id, 'name': user.company_id.name, 'displayName': user.company_id.display_name, 'phone': user.company_id.phone or None, 'city': user.company_id.city or None, 'website': user.company_id.website, 'state': { 'id': user.company_id.state_id.id, 'name': user.company_id.state_id.name, 'displayName': user.company_id.state_id.display_name } } } ''' Get currencies ''' def get_currencies(self): return [{ 'id': currency.id, 'name': currency.name, 'displayName': currency.display_name, 'base': currency.base, 'accuracy': currency.accuracy, 'rateSilent': currency.rate_silent, 'rounding': currency.rounding, 'symbol': currency.symbol, 'position': currency.position, 'decimalSeparator': currency.decimal_separator, 'decimalPlaces': currency.decimal_places, 'thousandsSeparator': currency.thousands_separator } for currency in request.env['res.currency'].search([('active', '=', True)])] ''' Get all active journals ''' def get_journals(self): return [{ 'id': journal.id, 'name': journal.name, 'displayName': journal.display_name, 'code': journal.code, 'cashControl': journal.cash_control, 'type': journal.type, 'currency': { 'id': journal.currency.id, 'name': journal.currency.name, 'displayName': journal.currency.display_name }, 'defaultCreditAccount': { 'id': journal.default_credit_account_id.id, 'name': journal.default_credit_account_id.name, 'displayName': journal.default_credit_account_id.display_name, 'code': journal.default_credit_account_id.code, 'exchangeRate': journal.default_credit_account_id.exchange_rate, 'foreignBalance': journal.default_credit_account_id.foreign_balance, 'reconcile': journal.default_credit_account_id.reconcile, 'debit': journal.default_credit_account_id.debit, 'credit': journal.default_credit_account_id.credit, 'currencyMode': journal.default_credit_account_id.currency_mode, 'companyCurrency': { 'id': journal.default_credit_account_id.company_currency_id.id, 'name': journal.default_credit_account_id.company_currency_id.name, 'displayName': journal.default_credit_account_id.company_currency_id.display_name, }, 'currency': { 'id': journal.default_credit_account_id.currency_id.id, 'name': journal.default_credit_account_id.currency_id.name, 'displayName': journal.default_credit_account_id.currency_id.display_name }, } } for journal in request.env['account.journal'].search([('type', 'in', ['bank', 'cash']), ('default_credit_account_id.currency_id', '=', False), ('active', '=', True)], order='id')] ''' Get banks ''' def get_banks(self): banks = [] if self.check_module('eiru_bank_payments_references'): banks = [{ 'id': bank.id, 'name': bank.name, 'displayName': 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.check_module('eiru_bank_payments_references'): bank_types = [{ 'id': type.id, 'name': type.name, 'displayName': type.display_name, 'code': type.code, 'defaultState': type.default_state } for type in request.env['res.bank.payments.type'].search([('is_receipt', '=', True)])] return bank_types ''' Get all active customers ''' def get_customers(self): return [{ 'id': customer.id, 'name': customer.name, 'displayName': customer.display_name, 'imageMedium': 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([('customer', '=', True), ('active', '=', True)])] ''' Get all saleable and active products ''' def get_products(self): return [{ 'id': product.id, 'name': product.name, 'displayName': product.display_name, 'ean13': product.ean13, 'defaultCode': product.default_code, 'imageMedium': 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.name, 'displayName': variant.display_name, 'ean13': variant.ean13, 'defaultCode': product.default_code, 'imageMedium': 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([('sale_ok', '=', True), ('list_price', '>', 0), ('active', '=', True)])] ''' Get all active payment terms ''' def get_payment_terms(self): return [{ 'id': payment_term.id, 'name': payment_term.name, 'displayName': 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([('active', '=', True)])] ''' 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) ''' 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') return self.make_gzip_response({ 'date': self.get_server_datetime(), 'user': self.get_user(), 'currencies': self.get_currencies(), 'journals': self.get_journals(), 'customers': self.get_customers(), 'products': self.get_products(), 'paymentTerms': self.get_payment_terms(), 'banks': self.get_banks(), 'bankPaymentTypes': self.get_bank_payment_types() }) ''' 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.name, 'displayName': customer.display_name, 'imageMedium': customer.image_medium, 'ruc': customer.ruc or None, 'phone': customer.phone or None, 'mobile': customer.mobile or None, 'email': customer.email or None } ''' 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'), 'list_price': float(kw.get('price')), 'ean13': kw.get('ean13') }) return { 'id': product.id, 'name': product.name, 'displayName': product.display_name, 'ean13': product.ean13, 'imageMedium': product.image_medium, 'listPrice': product.list_price, 'variantCount': product.product_variant_count, 'quantity': 1, 'price': product.list_price, 'discount': 0, 'variants': [{ 'id': variant.id, 'name': variant.name, 'displayName': variant.display_name, 'ean13': variant.ean13, 'imageMedium': variant.image_medium, 'listPrice': variant.list_price, 'quantity': 1, 'price': variant.list_price, 'discount': 0, } for variant in product.product_variant_ids if variant.active] } ''' 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 ''' 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): return request.env['sale.order'].create({ 'partner_id': partner_id, 'order_line': [[0, False, { 'product_id': int(line.get('id')), 'product_uom_qty': float(line.get('quantity')), 'price_unit': float(line.get('price')) }] 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', }) ''' 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(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) 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) invoice.check_tax_lines(compute_taxes) invoice._recompute_tax_amount() invoice_move_lines += request.env['account.invoice.tax'].move_line_get(invoice.id) total, total_currency, invoice_move_lines = invoice.compute_invoice_totals(invoice.company_id.currency_id, invoice.reference, invoice_move_lines) paid_percentage = paid_amount / round(total, decimal_precision) distributed_percentage = -(paid_percentage / len(invoice.payment_term.line_ids)) payment_lines = [] for line in invoice.payment_term.line_ids: date_due = (parse(date_today) + rd(days=line.days + line.days2)).strftime(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 = round(total * payment_line[1], decimal_precision) if current_price < 0.0: continue paid_amount += current_price invoice_move_lines.append({ 'type': 'dest', 'name': '/', 'price': current_price if payment_line[1] else round(total - paid_amount, decimal_precision) or total, 'account_id': invoice.account_id.id, 'date_maturity': payment_line[0], 'amount_currency': invoice.company_id.currency_id.compute(payment_line[1], invoice.currency_id) if invoice.currency_id != invoice.company_id.currency_id else False, 'currency_id': invoice.currency_id != invoice.company_id.currency_id 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 ''' 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(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() }