main.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. # -*- coding: utf-8 -*-
  2. from openerp import http
  3. from openerp.http import request
  4. from werkzeug.wrappers import Response
  5. from werkzeug.datastructures import Headers
  6. from datetime import datetime
  7. from dateutil.relativedelta import relativedelta as rd
  8. from dateutil.parser import parse
  9. from gzip import GzipFile
  10. from StringIO import StringIO as IO
  11. import simplejson as json
  12. import gzip
  13. import logging
  14. LOGGER = logging.getLogger(__name__)
  15. DATE_FORMAT = '%Y-%m-%d'
  16. GZIP_COMPRESSION_LEVEL = 9
  17. class Sales(http.Controller):
  18. '''
  19. Get server date
  20. '''
  21. def get_server_date(self):
  22. return datetime.now().strftime(DATE_FORMAT)
  23. '''
  24. Get current user information
  25. '''
  26. def get_user(self):
  27. user = request.env.user
  28. return {
  29. 'id': user.id,
  30. 'name': user.name,
  31. 'displayName': user.display_name,
  32. 'currency': {
  33. 'id': user.company_id.currency_id.id,
  34. 'name': user.company_id.currency_id.name,
  35. 'displayName': user.company_id.currency_id.display_name,
  36. 'symbol': user.company_id.currency_id.symbol
  37. }
  38. }
  39. '''
  40. Get currencies
  41. '''
  42. def get_currencies(self):
  43. return [{
  44. 'id': currency.id,
  45. 'name': currency.name,
  46. 'displayName': currency.display_name,
  47. 'base': currency.base,
  48. 'accuracy': currency.accuracy,
  49. 'rateSilent': currency.rate_silent,
  50. 'rounding': currency.rounding,
  51. 'symbol': currency.symbol,
  52. 'position': currency.position,
  53. 'decimalSeparator': currency.decimal_separator,
  54. 'decimalPlaces': currency.decimal_places,
  55. 'thousandsSeparator': currency.thousands_separator
  56. } for currency in request.env['res.currency'].search([('active', '=', True)])]
  57. '''
  58. Get all active journals
  59. '''
  60. def get_journals(self):
  61. return [{
  62. 'id': journal.id,
  63. 'name': journal.name,
  64. 'displayName': journal.display_name,
  65. 'code': journal.code,
  66. 'cashControl': journal.cash_control,
  67. 'type': journal.type,
  68. 'currency': {
  69. 'id': journal.currency.id,
  70. 'name': journal.currency.name,
  71. 'displayName': journal.currency.display_name
  72. },
  73. 'defaultCreditAccount': {
  74. 'id': journal.default_credit_account_id.id,
  75. 'name': journal.default_credit_account_id.name,
  76. 'displayName': journal.default_credit_account_id.display_name,
  77. 'code': journal.default_credit_account_id.code,
  78. 'exchangeRate': journal.default_credit_account_id.exchange_rate,
  79. 'foreignBalance': journal.default_credit_account_id.foreign_balance,
  80. 'reconcile': journal.default_credit_account_id.reconcile,
  81. 'debit': journal.default_credit_account_id.debit,
  82. 'credit': journal.default_credit_account_id.credit,
  83. 'currencyMode': journal.default_credit_account_id.currency_mode,
  84. 'companyCurrency': {
  85. 'id': journal.default_credit_account_id.company_currency_id.id,
  86. 'name': journal.default_credit_account_id.company_currency_id.name,
  87. 'displayName': journal.default_credit_account_id.company_currency_id.display_name,
  88. },
  89. 'currency': {
  90. 'id': journal.default_credit_account_id.currency_id.id,
  91. 'name': journal.default_credit_account_id.currency_id.name,
  92. 'displayName': journal.default_credit_account_id.currency_id.display_name
  93. },
  94. }
  95. } for journal in request.env['account.journal'].search([('type', 'in', ['bank', 'cash']), ('default_credit_account_id.currency_id', '=', False), ('active', '=', True)], order='id')]
  96. '''
  97. Get all active customers
  98. '''
  99. def get_customers(self):
  100. return [{
  101. 'id': customer.id,
  102. 'name': customer.name,
  103. 'displayName': customer.display_name,
  104. 'imageMedium': customer.image_medium,
  105. 'phone': customer.phone,
  106. 'mobile': customer.mobile,
  107. 'email': customer.email
  108. } for customer in request.env['res.partner'].search([('customer', '=', True), ('active', '=', True)])]
  109. '''
  110. Get all saleable and active products
  111. '''
  112. def get_products(self):
  113. return [{
  114. 'id': product.id,
  115. 'name': product.name,
  116. 'displayName': product.display_name,
  117. 'ean13': product.ean13,
  118. 'imageMedium': product.image_medium,
  119. 'listPrice': product.list_price,
  120. 'variantCount': product.product_variant_count,
  121. 'quantity': 1,
  122. 'price': product.list_price,
  123. 'discount': 0,
  124. 'variants': [{
  125. 'id': variant.id,
  126. 'name': variant.name,
  127. 'displayName': variant.display_name,
  128. 'ean13': variant.ean13,
  129. 'imageMedium': variant.image_medium,
  130. 'listPrice': variant.list_price,
  131. 'quantity': 1,
  132. 'price': variant.list_price,
  133. 'discount': 0,
  134. } for variant in product.product_variant_ids if variant.active]
  135. } for product in request.env['product.template'].search([('sale_ok', '=', True), ('list_price', '>', 0), ('active', '=', True)])]
  136. '''
  137. Get all active payment terms
  138. '''
  139. def get_payment_terms(self):
  140. return [{
  141. 'id': payment_term.id,
  142. 'name': payment_term.name,
  143. 'displayName': payment_term.display_name,
  144. 'lines': [{
  145. 'id': line.id,
  146. 'days': line.days,
  147. 'days2': line.days2,
  148. 'value': line.value,
  149. 'value_amount': line.value_amount
  150. } for line in payment_term.line_ids]
  151. } for payment_term in request.env['account.payment.term'].search([('active', '=', True)])]
  152. '''
  153. Make JSON response
  154. '''
  155. def make_json_response(self, data=None, status=200):
  156. return Response(json.dumps(data), status=status, content_type='application/json')
  157. '''
  158. Make GZIP to JSON response
  159. '''
  160. def make_gzip_response(self, data=None, status=200):
  161. gzip_buffer = IO()
  162. with GzipFile(mode='wb', compresslevel=GZIP_COMPRESSION_LEVEL, fileobj=gzip_buffer) as gzip_file:
  163. gzip_file.write(json.dumps(data))
  164. contents = gzip_buffer.getvalue()
  165. gzip_buffer.close()
  166. headers = Headers()
  167. headers.add('Content-Encoding', 'gzip')
  168. headers.add('Vary', 'Accept-Encoding')
  169. headers.add('Content-Length', len(contents))
  170. return Response(contents, status=status, headers=headers, content_type='application/json')
  171. '''
  172. '''
  173. def make_info_log(self, log):
  174. LOGGER.info(log)
  175. '''
  176. New purchase resource route
  177. '''
  178. @http.route('/eiru_sales/init', auth='user', methods=['GET'], cors='*')
  179. def init_sale(self, **kw):
  180. self.make_info_log('Sending JSON response')
  181. return self.make_gzip_response({
  182. 'date': self.get_server_date(),
  183. 'user': self.get_user(),
  184. 'currencies': self.get_currencies(),
  185. 'journals': self.get_journals(),
  186. 'customers': self.get_customers(),
  187. 'products': self.get_products(),
  188. 'paymentTerms': self.get_payment_terms()
  189. })
  190. '''
  191. Create customer and return data
  192. '''
  193. @http.route('/eiru_sales/create_customer', type='json', auth='user', methods=['POST'], cors='*')
  194. def create_customer(self, **kw):
  195. self.make_info_log('Creating customer')
  196. customer = request.env['res.partner'].create({
  197. 'name': kw.get('name'),
  198. 'ruc': kw.get('ruc'),
  199. 'phone': kw.get('phone'),
  200. 'customer': True
  201. })
  202. return {
  203. 'id': customer.id,
  204. 'name': customer.name,
  205. 'displayName': customer.display_name,
  206. 'imageMedium': customer.image_medium,
  207. 'phone': customer.phone,
  208. 'mobile': customer.mobile,
  209. 'email': customer.email
  210. }
  211. '''
  212. Create product and return data
  213. '''
  214. @http.route('/eiru_sales/create_product', type='json', auth='user', methods=['POST'], cors='*')
  215. def create_product(self, **kw):
  216. self.make_info_log('Creating customer')
  217. product = request.env['product.template'].create({
  218. 'name': kw.get('name'),
  219. 'list_price': float(kw.get('price')),
  220. 'ean13': kw.get('ean13')
  221. })
  222. return {
  223. 'id': product.id,
  224. 'name': product.name,
  225. 'displayName': product.display_name,
  226. 'ean13': product.ean13,
  227. 'imageMedium': product.image_medium,
  228. 'listPrice': product.list_price,
  229. 'variantCount': product.product_variant_count,
  230. 'quantity': 1,
  231. 'price': product.list_price,
  232. 'discount': 0,
  233. 'variants': [{
  234. 'id': variant.id,
  235. 'name': variant.name,
  236. 'displayName': variant.display_name,
  237. 'ean13': variant.ean13,
  238. 'imageMedium': variant.image_medium,
  239. 'listPrice': variant.list_price,
  240. 'quantity': 1,
  241. 'price': variant.list_price,
  242. 'discount': 0,
  243. } for variant in product.product_variant_ids if variant.active]
  244. }
  245. '''
  246. Get currency from journal
  247. '''
  248. def get_currency(self, journal_id):
  249. journal = request.env['account.journal'].browse(journal_id)
  250. return journal.default_credit_account_id.currency_id.id or journal.default_credit_account_id.company_currency_id.id
  251. '''
  252. Create sale order from cart items values
  253. '''
  254. def create_sale_from_cart(self, partner_id, cart_items, date_confirm, currency_id, payment_term_id):
  255. return request.env['sale.order'].create({
  256. 'partner_id': partner_id,
  257. 'order_line': [[0, False, {
  258. 'product_id': int(line.get('id')),
  259. 'product_uom_qty': float(line.get('quantity')),
  260. 'price_unit': float(line.get('price'))
  261. }] for line in cart_items],
  262. 'picking_policy': 'direct',
  263. 'state': 'manual',
  264. 'date_confirm': date_confirm,
  265. 'currency_id': currency_id,
  266. 'payment_term': payment_term_id
  267. })
  268. '''
  269. Confirm sale order
  270. '''
  271. def confirm_sale_order(self, sale_order_id):
  272. return request.env['sale.order'].browse(sale_order_id).action_button_confirm()
  273. '''
  274. Create invoice from sale order
  275. '''
  276. def create_invoice(self, sale_order_id, currency_id, date_today):
  277. sale_order = request.env['sale.order'].browse(sale_order_id)
  278. invoice_id = sale_order.action_invoice_create()
  279. invoice = request.env['account.invoice'].browse(invoice_id)
  280. date_due = parse(date_today) + rd(days=max(invoice.payment_term.line_ids.mapped(lambda x: x.days + x.days2)))
  281. invoice.write({
  282. 'currency_id': currency_id,
  283. 'date_invoice': date_today,
  284. 'date_due': date_due.strftime(DATE_FORMAT),
  285. 'state': 'open'
  286. })
  287. return invoice
  288. '''
  289. Create move lines
  290. '''
  291. def create_invoice_move_lines(self, invoice_id, paid_amount, date_today):
  292. invoice = request.env['account.invoice'].browse(invoice_id)
  293. invoice_move_lines = invoice._get_analytic_lines()
  294. decimal_precision = request.env['decimal.precision'].precision_get('Account')
  295. compute_taxes = request.env['account.invoice.tax'].compute(invoice)
  296. invoice.check_tax_lines(compute_taxes)
  297. invoice._recompute_tax_amount()
  298. invoice_move_lines += request.env['account.invoice.tax'].move_line_get(invoice.id)
  299. total, total_currency, invoice_move_lines = invoice.compute_invoice_totals(invoice.company_id.currency_id, invoice.reference, invoice_move_lines)
  300. paid_percentage = paid_amount / round(total, decimal_precision)
  301. distributed_percentage = -(paid_percentage / len(invoice.payment_term.line_ids))
  302. payment_lines = []
  303. for line in invoice.payment_term.line_ids:
  304. date_due = (parse(date_today) + rd(days=line.days + line.days2)).strftime(DATE_FORMAT)
  305. if paid_percentage and paid_percentage < 1.0:
  306. payment_lines.append([date_today, paid_percentage])
  307. paid_percentage = paid_amount = 0
  308. if date_due == date_today and line.value_amount:
  309. distributed_percentage = -((payment_lines[0][1] - line.value_amount) / (len(invoice.payment_term.line_ids) - 1))
  310. continue
  311. if line.value != 'balance':
  312. payment_lines.append([date_due, line.value_amount + distributed_percentage])
  313. continue
  314. payment_lines.append([date_due, line.value_amount])
  315. for payment_line in payment_lines:
  316. current_price = round(total * payment_line[1], decimal_precision)
  317. if current_price < 0.0:
  318. continue
  319. paid_amount += current_price
  320. invoice_move_lines.append({
  321. 'type': 'dest',
  322. 'name': '/',
  323. 'price': current_price if payment_line[1] else round(total - paid_amount, decimal_precision) or total,
  324. 'account_id': invoice.account_id.id,
  325. 'date_maturity': payment_line[0],
  326. '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,
  327. 'currency_id': invoice.currency_id != invoice.company_id.currency_id and invoice.currency_id.id,
  328. 'ref': invoice.reference
  329. })
  330. payment_lines = []
  331. return invoice_move_lines
  332. '''
  333. Create account move
  334. '''
  335. def create_account_move(self, invoice_id, invoice_move_lines):
  336. invoice = request.env['account.invoice'].browse(invoice_id)
  337. accounting_partner = request.env['res.partner']._find_accounting_partner(invoice.partner_id)
  338. move_line_values = [(0, 0, invoice.line_get_convert(line, accounting_partner.id, invoice.date_invoice)) for line in invoice_move_lines]
  339. move_line_values = invoice.group_lines(invoice_move_lines, move_line_values)
  340. move_line_values = invoice.finalize_invoice_move_lines(move_line_values)
  341. ctx = dict(request.context, lang=invoice.partner_id.lang, company_id=invoice.company_id.id)
  342. period = invoice.period_id
  343. if not period:
  344. period = period.with_context(ctx).find(invoice.date_invoice)[:1]
  345. if period:
  346. for line in move_line_values:
  347. line[2]['period_id'] = period.id
  348. ctx['invoice'] = invoice
  349. ctx_nolang = ctx.copy()
  350. ctx_nolang.pop('lang', None)
  351. account_move = request.env['account.move'].with_context(ctx_nolang).create({
  352. 'ref': invoice.reference or invoice.name,
  353. 'line_id': move_line_values,
  354. 'journal_id': invoice.journal_id.id,
  355. 'date': invoice.date_invoice,
  356. 'narration': invoice.comment,
  357. 'company_id': invoice.company_id.id,
  358. 'period_id': period.id
  359. })
  360. invoice.with_context(ctx).write({
  361. 'move_id': account_move.id,
  362. 'period_id': account_move.period_id.id,
  363. 'move_name': account_move.name,
  364. })
  365. account_move.post()
  366. return account_move
  367. '''
  368. Number to invoice
  369. '''
  370. def number_invoice(self, invoice_id):
  371. request.env['account.invoice'].browse(invoice_id).action_number()
  372. '''
  373. Create voucher
  374. '''
  375. def create_account_voucher(self, account_move_id, journal_id, currency_id, paid_amount):
  376. account_move = request.env['account.move'].browse(account_move_id)
  377. account_journal = request.env['account.journal'].browse(journal_id)
  378. account_voucher = request.env['account.voucher'].create({
  379. 'reference': account_move.name,
  380. 'type': 'receipt',
  381. 'journal_id': account_journal.id,
  382. 'company_id': account_move.company_id.id,
  383. 'pre_line': True,
  384. 'amount': paid_amount,
  385. 'period_id': account_move.period_id.id,
  386. 'date': account_move.date,
  387. 'partner_id': account_move.partner_id.id,
  388. 'account_id': account_journal.default_credit_account_id.id,
  389. 'currency_id': currency_id,
  390. 'line_cr_ids': [[0, False, {
  391. 'date_due': l.date_maturity,
  392. 'account_id': l.account_id.id,
  393. 'date_original': l.invoice.date_invoice,
  394. 'move_line_id': l.id,
  395. 'amount_original': abs(l.credit or l.debit or 0.0),
  396. 'amount_unreconciled': abs(l.amount_residual),
  397. 'amount': abs(l.debit) if account_move.date == l.date_maturity else 0.0,
  398. 'reconcile': account_move.date == l.date_maturity,
  399. 'currency_id': currency_id
  400. }] for l in account_move.line_id]
  401. })
  402. account_voucher.action_move_line_create()
  403. return account_voucher
  404. '''
  405. Close a invoice
  406. '''
  407. def close_invoice(self, invoice_id):
  408. invoice = request.env['account.invoice'].browse(invoice_id)
  409. if invoice.residual == 0:
  410. invoice.write({
  411. 'state': 'paid'
  412. })
  413. '''
  414. Create account bank statement lines
  415. '''
  416. def create_bank_statement_lines(self, account_voucher_id, reference=None):
  417. account_voucher = request.env['account.voucher'].browse(account_voucher_id)
  418. return [[0, False, {
  419. 'name': account_voucher.reference,
  420. 'amount': account_voucher.amount,
  421. 'partner_id': account_voucher.partner_id.id,
  422. 'voucher_id': account_voucher.id,
  423. 'journal_id': account_voucher.journal_id.id,
  424. 'account_id': account_voucher.account_id.id,
  425. 'journal_entry_id': account_voucher.move_id.id,
  426. 'currency_id': account_voucher.currency_id.id,
  427. 'ref': 'POS/' + (reference or '')
  428. }]]
  429. '''
  430. Create account bank statement
  431. '''
  432. def create_bank_statement(self, account_voucher_id, account_bank_statement_lines, date_today):
  433. account_voucher = request.env['account.voucher'].browse(account_voucher_id)
  434. account_bank_statement = request.env['account.bank.statement'].search([('journal_id', '=', account_voucher.journal_id.id), ('date', '=', date_today)])
  435. account_bank_statement_values = {
  436. 'date': date_today,
  437. 'user_id': request.env.user.id,
  438. 'journal_id': account_voucher.journal_id.id,
  439. 'period_id': account_voucher.period_id.id,
  440. 'line_ids': account_bank_statement_lines,
  441. 'state': 'open' if account_voucher.journal_id.type == 'cash' else 'draft'
  442. }
  443. if account_bank_statement:
  444. size = len(account_bank_statement)
  445. if size == 1:
  446. account_bank_statement.write(account_bank_statement_values)
  447. else:
  448. account_bank_statement[size - 1].write(account_bank_statement_values)
  449. else:
  450. account_bank_statement.create(account_bank_statement_values)
  451. return account_bank_statement
  452. '''
  453. Process sale
  454. '''
  455. @http.route('/eiru_sales/process_sale', type='json', auth='user', methods=['POST'], cors='*')
  456. def process_sale(self, **kw):
  457. self.make_info_log('Processing sale...')
  458. # Get date
  459. self.make_info_log('Getting date')
  460. date_now = datetime.now().strftime(DATE_FORMAT)
  461. # Get currency
  462. currency_id = self.get_currency(kw.get('journalId'))
  463. self.make_info_log('[OK] Getting journal')
  464. # Create sale orde
  465. sale_order = self.create_sale_from_cart(kw.get('customerId'), kw.get('items'), date_now, currency_id, kw.get('paymentTermId'))
  466. self.make_info_log('[OK] Creating sale order')
  467. # Confirm sale
  468. self.confirm_sale_order(sale_order.id)
  469. self.make_info_log('[OK] Confirm sale order')
  470. # Create invoice
  471. invoice = self.create_invoice(sale_order.id, currency_id, date_now)
  472. self.make_info_log('[OK] Creating invoice')
  473. # Create invoice move lines
  474. invoice_move_lines = self.create_invoice_move_lines(invoice.id, kw.get('total', 0), date_now)
  475. self.make_info_log('[OK] Creating invoice move lines')
  476. # Create account move
  477. account_move = self.create_account_move(invoice.id, invoice_move_lines)
  478. self.make_info_log('[OK] Creating account move')
  479. # Number invoice
  480. self.number_invoice(invoice.id)
  481. self.make_info_log('[OK] Number invoice')
  482. # Create account voucher
  483. account_voucher = self.create_account_voucher(account_move.id, kw.get('journalId'), currency_id, float(kw.get('payment')))
  484. self.make_info_log('[OK] Creating account voucher')
  485. # Close invoice
  486. self.close_invoice(invoice.id)
  487. self.make_info_log('[OK] Closing invoice')
  488. # Create account bank statement lines
  489. account_bank_statement_lines = self.create_bank_statement_lines(account_voucher.id)
  490. self.make_info_log('[OK] Creating account bank statement lines')
  491. # Create bank statement
  492. self.create_bank_statement(account_voucher.id, account_bank_statement_lines, date_now)
  493. self.make_info_log('[OK] Creating account bank statement')
  494. return {
  495. 'process': True
  496. }