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.

325 lines
13 KiB

  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Author: Vincent Renaville, ported by Joel Grand-Guillaume
  5. # Copyright 2010-2012 Camptocamp SA
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. from openerp.osv import orm, fields
  22. class AccountHoursBlock(orm.Model):
  23. _name = "account.hours.block"
  24. def _get_last_action(self, cr, uid, ids, name, arg, context=None):
  25. """ Return the last analytic line date for an invoice"""
  26. res = {}
  27. for block in self.browse(cr, uid, ids, context=context):
  28. cr.execute("SELECT max(al.date) FROM account_analytic_line AS al"
  29. " WHERE al.invoice_id = %s", (block.invoice_id.id,))
  30. fetch_res = cr.fetchone()
  31. res[block.id] = fetch_res[0] if fetch_res else False
  32. return res
  33. def _compute_hours(self, cr, uid, ids, fields, args, context=None):
  34. """Return a dict of [id][fields]"""
  35. if isinstance(ids, (int, long)):
  36. ids = [ids]
  37. result = {}
  38. aal_obj = self.pool.get('account.analytic.line')
  39. for block in self.browse(cr, uid, ids, context=context):
  40. result[block.id] = {'amount_hours_block': 0.0,
  41. 'amount_hours_block_done': 0.0}
  42. # Compute hours bought
  43. for line in block.invoice_id.invoice_line:
  44. hours_bought = 0.0
  45. if line.product_id:
  46. # We will now calculate the product_quantity
  47. factor = line.uos_id.factor
  48. if factor == 0.0:
  49. factor = 1.0
  50. amount = line.quantity
  51. hours_bought += (amount / factor)
  52. result[block.id]['amount_hours_block'] += hours_bought
  53. # Compute hours spent
  54. hours_used = 0.0
  55. # Get ids of analytic line generated from
  56. # timesheet associated to the current block
  57. cr.execute("SELECT al.id "
  58. "FROM account_analytic_line AS al, "
  59. " account_analytic_journal AS aj "
  60. "WHERE aj.id = al.journal_id "
  61. "AND aj.type = 'general' "
  62. "AND al.invoice_id = %s", (block.invoice_id.id,))
  63. res_line_ids = cr.fetchall()
  64. line_ids = [l[0] for l in res_line_ids] if res_line_ids else []
  65. for line in aal_obj.browse(cr, uid, line_ids, context=context):
  66. factor = 1.0
  67. if line.product_uom_id and line.product_uom_id.factor != 0.0:
  68. factor = line.product_uom_id.factor
  69. factor_invoicing = 1.0
  70. if line.to_invoice and line.to_invoice.factor != 0.0:
  71. factor_invoicing = 1.0 - line.to_invoice.factor / 100
  72. hours_used += ((line.unit_amount / factor) * factor_invoicing)
  73. result[block.id]['amount_hours_block_done'] = hours_used
  74. return result
  75. def _compute_amount(self, cr, uid, ids, fields, args, context=None):
  76. if context is None:
  77. context = {}
  78. result = {}
  79. aal_obj = self.pool.get('account.analytic.line')
  80. pricelist_obj = self.pool.get('product.pricelist')
  81. for block in self.browse(cr, uid, ids, context=context):
  82. result[block.id] = {'amount_hours_block': 0.0,
  83. 'amount_hours_block_done': 0.0}
  84. # Compute amount bought
  85. for line in block.invoice_id.invoice_line:
  86. amount_bought = 0.0
  87. if line.product_id:
  88. ## We will now calculate the product_quantity
  89. factor = line.uos_id.factor
  90. if factor == 0.0:
  91. factor = 1.0
  92. amount = line.quantity * line.price_unit
  93. amount_bought += (amount / factor)
  94. result[block.id]['amount_hours_block'] += amount_bought
  95. # Compute total amount
  96. # Get ids of analytic line generated from timesheet associated to current block
  97. cr.execute("SELECT al.id FROM account_analytic_line AS al,"
  98. " account_analytic_journal AS aj"
  99. " WHERE aj.id = al.journal_id"
  100. " AND aj.type='general'"
  101. " AND al.invoice_id = %s", (block.invoice_id.id,))
  102. res_line_ids = cr.fetchall()
  103. line_ids = [l[0] for l in res_line_ids] if res_line_ids else []
  104. total_amount = 0.0
  105. for line in aal_obj.browse(cr, uid, line_ids, context=context):
  106. factor_invoicing = 1.0
  107. if line.to_invoice and line.to_invoice.factor != 0.0:
  108. factor_invoicing = 1.0 - line.to_invoice.factor / 100
  109. ctx = dict(context, uom=line.product_uom_id.id)
  110. amount = pricelist_obj.price_get(
  111. cr, uid,
  112. [line.account_id.pricelist_id.id],
  113. line.product_id.id,
  114. line.unit_amount or 1.0,
  115. line.account_id.partner_id.id or False,
  116. ctx)[line.account_id.pricelist_id.id]
  117. total_amount += amount * line.unit_amount * factor_invoicing
  118. result[block.id]['amount_hours_block_done'] += total_amount
  119. return result
  120. def _compute(self, cr, uid, ids, fields, args, context=None):
  121. result = {}
  122. block_per_types = {}
  123. for block in self.browse(cr, uid, ids, context=context):
  124. block_per_types.setdefault(block.type, []).append(block.id)
  125. for block_type in block_per_types:
  126. if block_type:
  127. func = getattr(self, "_compute_%s" % block_type)
  128. result.update(func(cr, uid, ids, fields, args, context=context))
  129. for block in result:
  130. result[block]['amount_hours_block_delta'] = \
  131. result[block]['amount_hours_block'] - \
  132. result[block]['amount_hours_block_done']
  133. return result
  134. def _get_analytic_line(self, cr, uid, ids, context=None):
  135. invoice_ids = []
  136. an_lines_obj = self.pool.get('account.analytic.line')
  137. block_obj = self.pool.get('account.hours.block')
  138. for line in an_lines_obj.browse(cr, uid, ids, context=context):
  139. if line.invoice_id:
  140. invoice_ids.append(line.invoice_id.id)
  141. return block_obj.search(
  142. cr, uid, [('invoice_id', 'in', invoice_ids)], context=context)
  143. def _get_invoice(self, cr, uid, ids, context=None):
  144. block_ids = set()
  145. inv_obj = self.pool.get('account.invoice')
  146. for invoice in inv_obj.browse(cr, uid, ids, context=context):
  147. block_ids.update([inv.id for inv in invoice.account_hours_block_ids])
  148. return list(block_ids)
  149. _recompute_triggers = {
  150. 'account.hours.block': (lambda self, cr, uid, ids, c=None:
  151. ids, ['invoice_id', 'type'], 10),
  152. 'account.invoice': (_get_invoice, ['analytic_line_ids'], 10),
  153. 'account.analytic.line': (
  154. _get_analytic_line,
  155. ['product_uom_id', 'unit_amount', 'to_invoice', 'invoice_id'],
  156. 10),
  157. }
  158. _columns = {
  159. 'amount_hours_block': fields.function(
  160. _compute,
  161. type='float',
  162. string='Quantity / Amount bought',
  163. store=_recompute_triggers,
  164. multi='amount_hours_block_delta',
  165. help="Amount bought by the customer. "
  166. "This amount is expressed in the base Unit of Measure "
  167. "(factor=1.0)"),
  168. 'amount_hours_block_done': fields.function(
  169. _compute,
  170. type='float',
  171. string='Quantity / Amount used',
  172. store=_recompute_triggers,
  173. multi='amount_hours_block_delta',
  174. help="Amount done by the staff. "
  175. "This amount is expressed in the base Unit of Measure "
  176. "(factor=1.0)"),
  177. 'amount_hours_block_delta': fields.function(
  178. _compute,
  179. type='float',
  180. string='Difference',
  181. store=_recompute_triggers,
  182. multi='amount_hours_block_delta',
  183. help="Difference between bought and used. "
  184. "This amount is expressed in the base Unit of Measure "
  185. "(factor=1.0)"),
  186. 'last_action_date': fields.function(
  187. _get_last_action,
  188. type='date',
  189. string='Last action date',
  190. help="Date of the last analytic line linked to the invoice "
  191. "related to this block hours."),
  192. 'close_date': fields.date('Closed Date'),
  193. 'invoice_id': fields.many2one(
  194. 'account.invoice',
  195. 'Invoice',
  196. ondelete='cascade',
  197. required=True),
  198. 'type': fields.selection(
  199. [('hours', 'Hours'),
  200. ('amount', 'Amount')],
  201. string='Type of Block',
  202. required=True,
  203. help="The block is based on the quantity of hours "
  204. "or on the amount."),
  205. # Invoices related infos
  206. 'date_invoice': fields.related(
  207. 'invoice_id', 'date_invoice',
  208. type="date",
  209. string="Invoice Date",
  210. store=True,
  211. readonly=True),
  212. 'user_id': fields.related(
  213. 'invoice_id', 'user_id',
  214. type="many2one",
  215. relation="res.users",
  216. string="Salesman",
  217. store=True,
  218. readonly=True),
  219. 'partner_id': fields.related(
  220. 'invoice_id', 'partner_id',
  221. type="many2one",
  222. relation="res.partner",
  223. string="Partner",
  224. store=True,
  225. readonly=True),
  226. 'name': fields.related(
  227. 'invoice_id', 'name',
  228. type="char",
  229. string="Description",
  230. store=True,
  231. readonly=True),
  232. 'number': fields.related(
  233. 'invoice_id', 'number',
  234. type="char",
  235. string="Number",
  236. store=True,
  237. readonly=True),
  238. 'journal_id': fields.related(
  239. 'invoice_id', 'journal_id',
  240. type="many2one",
  241. relation="account.journal",
  242. string="Journal",
  243. store=True,
  244. readonly=True),
  245. 'period_id': fields.related(
  246. 'invoice_id', 'period_id',
  247. type="many2one",
  248. relation="account.period",
  249. string="Period",
  250. store=True,
  251. readonly=True),
  252. 'company_id': fields.related(
  253. 'invoice_id', 'company_id',
  254. type="many2one",
  255. relation="res.company",
  256. string="Company",
  257. store=True,
  258. readonly=True),
  259. 'currency_id': fields.related(
  260. 'invoice_id', 'currency_id',
  261. type="many2one",
  262. relation="res.currency",
  263. string="Currency",
  264. store=True,
  265. readonly=True),
  266. 'residual': fields.related(
  267. 'invoice_id', 'residual',
  268. type="float",
  269. string="Residual",
  270. store=True,
  271. readonly=True),
  272. 'amount_total': fields.related(
  273. 'invoice_id', 'amount_total',
  274. type="float",
  275. string="Total",
  276. store=True,
  277. readonly=True),
  278. 'state': fields.related(
  279. 'invoice_id', 'state',
  280. type='selection',
  281. selection=[
  282. ('draft', 'Draft'),
  283. ('proforma', 'Pro-forma'),
  284. ('proforma2', 'Pro-forma'),
  285. ('open', 'Open'),
  286. ('paid', 'Paid'),
  287. ('cancel', 'Cancelled'),
  288. ],
  289. string='State',
  290. readonly=True,
  291. store=True),
  292. }
  293. ############################################################################
  294. ## Add hours blocks on invoice
  295. ############################################################################
  296. class AccountInvoice(orm.Model):
  297. _inherit = 'account.invoice'
  298. _columns = {
  299. 'account_hours_block_ids': fields.one2many(
  300. 'account.hours.block',
  301. 'invoice_id',
  302. string='Hours Block')
  303. }