You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
11 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # OpenERP, Open Source Management Solution
  5. # Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
  6. # Copyright (C) 2015 initOS GmbH & Co. KG (<http://www.initos.com>).
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Affero General Public License as
  10. # published by the Free Software Foundation, either version 3 of the
  11. # License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Affero General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. ##############################################################################
  22. from datetime import datetime, timedelta
  23. from lxml import etree
  24. from openerp.osv import orm, fields
  25. from openerp.tools.translate import _
  26. from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
  27. class PosTopSellersProductReport(orm.Model):
  28. _name = 'pos.top.sellers.product.report'
  29. # Create no table. Everything is created dynamically in this model
  30. _auto = False
  31. _columns = dict(date=fields.char(string='', readonly=True))
  32. def get_product_code_for_id(self, cr, uid, product_id, context=None):
  33. prod = self.pool['product.product'].\
  34. browse(cr, uid, product_id, context)
  35. return prod.default_code
  36. def get_product_id_for_code(self, cr, uid, default_code, context=None):
  37. ids = self.pool['product.product'].\
  38. search(cr, uid, [('default_code', '=', default_code)], context)
  39. return ids[0] if ids else 0
  40. def fields_view_get(self, cr, uid, view_id=None, view_type='form',
  41. context=None, toolbar=False, submenu=False):
  42. res = super(PosTopSellersProductReport, self). \
  43. fields_view_get(cr, uid, view_id=view_id, view_type=view_type,
  44. context=context, toolbar=toolbar)
  45. if view_type == 'tree':
  46. shop_model = self.pool['sale.shop']
  47. shop_ids = shop_model.search(cr, uid, [])
  48. arch = etree.XML(res['arch'])
  49. tree = arch.xpath("//tree")
  50. for shop in shop_model.browse(cr, uid, shop_ids):
  51. # create fields for shop
  52. qty_key = 'qty_' + str(shop.id)
  53. res['fields'].update({
  54. qty_key: dict(
  55. string=shop.name,
  56. type='integer',
  57. readonly='True'
  58. )
  59. })
  60. # add field to tree
  61. etree.SubElement(tree[0], 'field', dict(
  62. name=qty_key
  63. ))
  64. res['arch'] = etree.tostring(arch)
  65. return res
  66. def _get_context_date_range(self, cr, uid, context=None):
  67. """
  68. Check date range from context and create date range for the past
  69. 30 days if date range is missing in context.
  70. """
  71. date_from = context and context.get('list_date_range_bar_start')
  72. date_to = context and context.get('list_date_range_bar_end')
  73. if not date_to:
  74. date_to = fields.date.context_today(self, cr, uid, context=context)
  75. if not date_from:
  76. timestamp = datetime.strptime(date_to, DEFAULT_SERVER_DATE_FORMAT)
  77. timestamp -= timedelta(days=30)
  78. date_from = fields.date.context_today(self, cr, uid,
  79. context=context,
  80. timestamp=timestamp)
  81. return date_from, date_to
  82. def search(self, cr, user, args, offset=0, limit=None, order=None,
  83. context=None, count=False):
  84. product_id = context and context.get('my_res_id')
  85. date_from, date_to = self._get_context_date_range(cr, user,
  86. context=context)
  87. res = []
  88. if product_id and date_from and date_to:
  89. d0 = datetime.strptime(date_from, DEFAULT_SERVER_DATE_FORMAT)
  90. d1 = datetime.strptime(date_to, DEFAULT_SERVER_DATE_FORMAT)
  91. # range depends on number of days in date range
  92. num_days = abs((d1 - d0).days) + 1
  93. res = range(1, 1 + 2 + num_days)
  94. return res
  95. def read(self, cr, user, ids, fields=None, context=None,
  96. load='_classic_read'):
  97. # create empty result lines
  98. res = [dict(id=i) for i in ids]
  99. product_id = context and context.get('my_res_id')
  100. date_from, date_to = self._get_context_date_range(cr, user,
  101. context=context)
  102. if not (product_id and date_from and date_to):
  103. return res
  104. # first two lines are summary of sales and stock
  105. res[0].update(date=_('Sold'))
  106. res[1].update(date=_('Currently in stock'))
  107. # remaining lines are sales top statistics per date
  108. for shop_id in self.pool['sale.shop'].\
  109. search(cr, user, [], context=context):
  110. sql = '''
  111. select
  112. date_trunc('day', dd)::date as date
  113. ,COALESCE(pd.qty, 0) as qty
  114. from generate_series
  115. ( %(date_from)s
  116. , %(date_to)s
  117. , '1 day'::interval) as dd
  118. left join (
  119. select
  120. po.date_order::date as date
  121. ,sum(pol.qty) as qty
  122. from pos_order_line pol
  123. join pos_order po
  124. on po.id = pol.order_id
  125. join product_product pp
  126. on pp.id = product_id
  127. join product_template pt
  128. on pp.product_tmpl_id = pt.id
  129. where pt.list_price > 0 and
  130. shop_id = %(shop_id)s and product_id = %(product_id)s
  131. group by
  132. po.date_order::date
  133. ) as pd
  134. on pd.date = dd.date
  135. order by
  136. date desc
  137. '''
  138. cr.execute(sql, dict(shop_id=shop_id, product_id=product_id,
  139. date_from=date_from, date_to=date_to))
  140. query_result = cr.fetchall()
  141. qty_key = 'qty_' + str(shop_id)
  142. line_id = 2
  143. for date, qty in query_result:
  144. res[line_id].update({
  145. 'date': date,
  146. qty_key: qty
  147. })
  148. total_qty = res[0].get(qty_key, 0) + qty
  149. res[0].update({qty_key: total_qty})
  150. line_id += 1
  151. ctx = dict(context or {})
  152. ctx.update({'shop': shop_id,
  153. 'states': ('done',),
  154. 'what': ('in', 'out')})
  155. product = self.pool['product.product'].\
  156. browse(cr, user, product_id, context=ctx)
  157. res[1].update({qty_key: product.qty_available})
  158. # postprocess
  159. for line in res[2:]:
  160. # transform date format
  161. # fixme: note this uses the server locale not the user language
  162. # set in odoo to really do this right we need to format this in JS
  163. # on the client side
  164. date = datetime.strptime(line['date'], '%Y-%m-%d')
  165. line['date'] = date.strftime('%A, %x')
  166. return res
  167. class PosTopSellersShopReport(orm.Model):
  168. _name = 'pos.top.sellers.shop.report'
  169. # We do not have columns. Everything is created dynamically in this model
  170. _auto = False
  171. # by default list the top 40 products
  172. _top_ten_limit = 40
  173. def fields_view_get(self, cr, uid, view_id=None, view_type='form',
  174. context=None, toolbar=False, submenu=False):
  175. res = super(PosTopSellersShopReport, self).\
  176. fields_view_get(cr, uid, view_id=view_id, view_type=view_type,
  177. context=context, toolbar=toolbar)
  178. if view_type == 'tree':
  179. shop_model = self.pool['sale.shop']
  180. shop_ids = shop_model.search(cr, uid, [])
  181. arch = etree.XML(res['arch'])
  182. tree = arch.xpath("//tree")
  183. for shop in shop_model.browse(cr, uid, shop_ids):
  184. # create fields for shop
  185. product_key = 'product_id_' + str(shop.id)
  186. qty_key = 'qty_' + str(shop.id)
  187. res['fields'].update({
  188. product_key: dict(
  189. string=shop.name,
  190. type='many2one',
  191. relation='product.product',
  192. readonly='True',
  193. ),
  194. qty_key: dict(
  195. string=_('QT'),
  196. type='integer',
  197. readonly='True'
  198. )
  199. })
  200. # add field to tree
  201. etree.SubElement(tree[0], 'field', dict(
  202. name=product_key,
  203. widget="pos_top_sellers_product_col",
  204. ))
  205. etree.SubElement(tree[0], 'field', dict(
  206. name=qty_key
  207. ))
  208. res['arch'] = etree.tostring(arch)
  209. return res
  210. def search(self, cr, user, args, offset=0, limit=None, order=None,
  211. context=None, count=False):
  212. # ignore sorting, limit etc
  213. return range(1, 1 + self._top_ten_limit)
  214. def read(self, cr, user, ids, fields=None, context=None,
  215. load='_classic_read'):
  216. date_from = context and context.get('list_date_range_bar_start')
  217. date_to = context and context.get('list_date_range_bar_end')
  218. # create empty result lines
  219. res = [dict(id=i) for i in ids]
  220. limit = len(res)
  221. for shop_id in self.pool['sale.shop'].\
  222. search(cr, user, [], context=context):
  223. sql = '''
  224. select
  225. product_id
  226. ,COALESCE(default_code, pt.name)
  227. ,sum(pol.qty) as qty
  228. from pos_order_line pol
  229. join pos_order po
  230. on po.id = pol.order_id
  231. join product_product pp
  232. on pp.id = product_id
  233. join product_template pt
  234. on pp.product_tmpl_id = pt.id
  235. where pt.list_price > 0 and
  236. shop_id = %(shop_id)s
  237. '''
  238. if date_from:
  239. sql += '''and po.date_order::date >= %(date_from)s '''
  240. if date_to:
  241. sql += '''and po.date_order::date <= %(date_to)s '''
  242. sql += \
  243. '''
  244. group by
  245. product_id
  246. ,COALESCE(default_code, pt.name)
  247. order by
  248. qty desc
  249. fetch first %(limit)s rows only
  250. '''
  251. cr.execute(sql, dict(shop_id=shop_id, limit=limit,
  252. date_from=date_from, date_to=date_to))
  253. product_key = 'product_id_' + str(shop_id)
  254. qty_key = 'qty_' + str(shop_id)
  255. line_id = 0
  256. for product_id, default_code, qty in cr.fetchall():
  257. res[line_id].update({
  258. product_key: (product_id, default_code),
  259. qty_key: qty
  260. })
  261. line_id += 1
  262. return res