# -*- 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 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 class Purchases(http.Controller): ''' Get server date to send ''' def get_server_date(self): return datetime.now().strftime(DATE_FORMAT) ''' 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 } } ''' 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 }, 'defaultDebitAccount': { 'id': journal.default_debit_account_id.id, 'name': journal.default_debit_account_id.name, 'displayName': journal.default_debit_account_id.display_name, 'code': journal.default_debit_account_id.code, 'exchange_rate': 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_debit_account_id.currency_id', '=', False), ('active', '=', True)], order='id')] ''' Get all active suppliers ''' def get_suppliers(self): return [{ 'id': supplier.id, 'name': supplier.name, 'displayName': supplier.display_name, 'imageMedium': supplier.image_medium, 'phone': supplier.phone, 'mobile': supplier.mobile, 'email': supplier.email } for supplier in request.env['res.partner'].search([('supplier', '=', True), ('active', '=', True)])] ''' Get all purchasable and active products ''' def get_products(self): return [{ 'id': product.id, 'name': product.name, 'displayName': product.display_name, 'ean13': product.ean13, 'imageMedium': product.image_medium, 'standardPrice': product.standard_price, 'variantCount': product.product_variant_count, 'quantity': 1, 'price': product.standard_price, 'variants': [{ 'id': variant.id, 'name': variant.name, 'displayName': variant.display_name, 'ean13': variant.ean13, 'imageMedium': variant.image_medium, 'standardPrice': variant.standard_price, 'quantity': 1, 'price': variant.standard_price } for variant in product.product_variant_ids if variant.active] } for product in request.env['product.template'].search([('purchase_ok', '=', True), ('standard_price', '>=', 0), ('active', '=', True)])] ''' Get all incoming and active picking types ''' def get_picking_types(self): return [{ 'id': picking_type.id, 'name': picking_type.name, 'displayName': picking_type.display_name } for picking_type in request.env['stock.picking.type'].search([('code', '=', 'incoming'), ('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 to send ''' def make_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('\033[1;34m[INFO] --> \033[m{}'.format(log)) ''' New purchase resource route ''' @http.route('/eiru_purchases/init', auth='user', methods=['GET'], cors='*') def init_purchase(self, **kw): self.make_info_log('Sending JSON response') return self.make_gzip_response({ 'date': self.get_server_date(), 'user': self.get_user(), 'currencies': self.get_currencies(), 'journals': self.get_journals(), 'suppliers': self.get_suppliers(), 'products': self.get_products(), 'pickingTypes': self.get_picking_types(), 'paymentTerms': self.get_payment_terms() }) ''' Create supplier and return data ''' @http.route('/eiru_purchases/create_supplier', type='json', auth='user', methods=['POST'], cors='*') def create_supplier(self, **kw): self.make_info_log('Creating supplier') supplier = request.env['res.partner'].create({ 'name': kw.get('name'), 'ruc': kw.get('ruc'), 'phone': kw.get('phone'), 'supplier': True, 'customer': False }) return { 'id': supplier.id, 'name': supplier.name, 'displayName': supplier.display_name, 'imageMedium': supplier.image_medium, 'phone': supplier.phone, 'mobile': supplier.mobile, 'email': supplier.email } ''' Create product and return data ''' @http.route('/eiru_purchases/create_product', type='json', auth='user', methods=['POST'], cors='*') def create_product(self, **kw): self.make_info_log('Creating product') product = request.env['product.template'].create({ 'name': kw.get('name'), 'standard_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, 'variants': [{ 'id': variant.id, 'name': variant.name, 'displayName': variant.display_name, 'ean13': variant.ean13, 'imageMedium': variant.image_medium, 'standardPrice': variant.standard_price } for variant in product.product_variant_ids if variant.active] } ''' Get currency id based on journal ''' def get_currency_id(self, journal_id): journal = request.env['account.journal'].browse(journal_id) return journal.default_debit_account_id.currency_id.id or journal.default_debit_account_id.company_currency_id.id ''' Check currency in pricelist and return it ''' def get_pricelist_id(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.id ''' Get default location ''' def get_stock_location_id(self): stock_location = request.env['stock.location'].search([('usage', '=', 'internal')]) return stock_location.id ''' Create purchase order from cart and return id ''' def create_purchase_order(self, supplier_id, cart_items, date_order, currency_id, pricelist_id, payment_term_id=None): return request.env['purchase.order'].create({ 'name': '/', 'partner_id': supplier_id, 'order_line': [[0, False, { 'name': line.get('name'), 'date_planned': date_order, 'product_id': int(line.get('id')), 'product_qty': float(line.get('quantity')), 'price_unit': float(line.get('price')) }] for line in cart_items], 'date_order': datetime.now().strftime(DATETIME_FORMAT), 'currency_id': currency_id, 'pricelist_id': pricelist_id, 'payment_term_id': payment_term_id, 'location_id': self.get_stock_location_id(), 'invoice_method': 'order', 'state': 'draft' }) ''' Confirm purchase order ''' def confirm_purchase_order(self, purchase_order_id): purchase_order = request.env['purchase.order'].browse(purchase_order_id) purchase_order.action_purchase_confirm() for picking in purchase_order.picking_ids: picking.force_assign() picking.action_done() ''' Purchase order ''' def create_invoice_lines(self, purchase_order_id): purchase_order_obj = request.env['purchase.order'] account_invoice_line_obj = request.env['account.invoice.line'] purchase_order = purchase_order_obj.browse(purchase_order_id) invoice_lines = {} for line in purchase_order.order_line: if (not line.invoiced) and (line.state not in ('draft', 'cancel')): if not line.partner_id.id in invoice_lines: invoice_lines[line.partner_id.id] = [] account_id = purchase_order_obj._choose_account_from_po_line(line) account_invoice_line_values = purchase_order_obj._prepare_inv_line(account_id, line) account_invoice_line_values.update({ 'origin': line.order_id.name }) account_invoice_line_id = account_invoice_line_obj.create(account_invoice_line_values) line.write({ 'invoiced': True, 'invoice_lines': [(4, account_invoice_line_id.id)] }) invoice_lines[line.partner_id.id].append((line, account_invoice_line_id.id)) return invoice_lines ''' Prepare invoice ''' def prepare_invoice(self, purchase_order_id, invoice_lines): purchase_order = request.env['purchase.order'].browse(purchase_order_id) assert len(purchase_order.invoice_ids) == 1 journal = request.env['account.journal'].search([('type', '=', 'purchase')]) date_due = parse(purchase_order.date_approve) + rd(days=max(purchase_order.payment_term_id.line_ids.mapped(lambda x: x.days + x.days2))) for value in invoice_lines.values(): lines = map(lambda x : x[1], value) orders = list(set(map(lambda x : x[0].order_id, value))) purchase_order.invoice_ids.write({ 'name': orders[0].name or '', 'origin': orders[0].name or '', 'type': 'in_invoice', 'journal_id': journal.id or False, 'reference': purchase_order.partner_id.ref, 'account_id': purchase_order.partner_id.property_account_payable.id, 'invoice_line': [(6, 0, lines)], 'currency_id': purchase_order.currency_id.id, 'payment_term': purchase_order.payment_term_id.id, 'fiscal_position': purchase_order.partner_id.property_account_position.id, 'date_invoice': purchase_order.date_approve, 'date_due': date_due, 'state': 'open' }) ''' Create move lines ''' def create_invoice_move_lines(self, invoice_ids, paid_amount, date_today): assert len(invoice_ids) == 1 invoice = request.env['account.invoice'].browse(invoice_ids) 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_ids, invoice_move_lines): assert len(invoice_ids) == 1 invoice = request.env['account.invoice'].browse(invoice_ids) 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_ids): assert len(invoice_ids) == 1 request.env['account.invoice'].browse(invoice_ids).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_ids): assert len(invoice_ids) == 1 invoice = request.env['account.invoice'].browse(invoice_ids) if invoice.residual == 0: invoice.write({ 'state': 'paid' }) ''' 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 ''' 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 '') }]] ''' Purchase processing resource route ''' @http.route('/eiru_purchases/process', type='json', auth='user', methods=['POST'], cors='*') def process_purchase(self, **kw): self.make_info_log('Processing purchase...') # Get date date_now = datetime.now().strftime(DATE_FORMAT) self.make_info_log('Getting date') # Get currency currency_id = self.get_currency_id(kw.get('journalId')) self.make_info_log('Getting currency') # Get pricelist pricelist_id = self.get_pricelist_id(currency_id) self.make_info_log('Product pricelist checked') # Create purchase order purchase_order = self.create_purchase_order(kw.get('supplierId'), kw.get('items'), date_now, currency_id, pricelist_id, kw.get('paymentTermId')) self.make_info_log('Purchase order created') # Confirm purchase self.confirm_purchase_order(purchase_order.id) self.make_info_log('Purchase order confirmed') # Create invoice lines invoice_lines = self.create_invoice_lines(purchase_order.id) self.make_info_log('Invoice lines created') # Invoice preparation self.prepare_invoice(purchase_order.id, invoice_lines) self.make_info_log('Invoice prepared') # # Create invoice # invoice_ids = self.create_invoice(purchase_order.id) # print(invoice_ids) # self.make_info_log('Invoice created') # # Prepare invoice # self.prepare_invoice(invoice_ids, currency_id, date_now) # self.make_info_log('Invoice prepared') # # Create invoice move line # invoice_move_lines = self.create_invoice_move_lines(invoice_ids, float(kw.get('payment')), date_now) # self.make_info_log('Invoice move lines created') # # Create account move # account_move = self.create_account_move(invoice_ids, invoice_move_lines) # self.make_info_log('Account move created') # # Number invoice # self.number_invoice(invoice_ids) # self.make_info_log('Number invoice ok') # # 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('Account voucher created') # # Close invoice # self.close_invoice(invoice_ids) # self.make_info_log('Attempt close invoice') # # Create account bank statement lines # account_bank_statement_lines = self.create_bank_statement_lines(account_voucher.id) # self.make_info_log('Bank statement lines created') # # Create account bank statement # self.create_bank_statement(account_voucher.id, account_bank_statement_lines, date_now) # self.make_info_log('Bank statement created') return { 'status': 'ok' }