purchse_analytics.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. # -*- coding: utf-8 -*-
  2. # @authors: Alexander Ezquevo <alexander@acysos.com>
  3. # Copyright (C) 2015 Acysos S.L.
  4. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  5. from openerp import models, fields, api, sql_db, _
  6. from openerp.http import request
  7. from openerp.exceptions import Warning
  8. from datetime import datetime, timedelta
  9. import thread
  10. from docutils.nodes import transition
  11. DFORMAT = "%Y-%m-%d %H:%M:%S"
  12. DAFORTMAT = "%Y-%m-%d"
  13. class purchase_order_line(models.Model):
  14. _inherit = 'purchase.order.line'
  15. @api.multi
  16. @api.depends('order_id.imputed')
  17. def _get_imputed(self):
  18. for res in self:
  19. if res.order_id.imputed:
  20. res.imputed = res.order_id.imputed
  21. @api.multi
  22. @api.depends('price_unit', 'product_qty')
  23. def _get_aux_price_subtotal(self):
  24. for res in self:
  25. if res.price_unit and res.product_qty:
  26. res.aux_price_subtotal = res.price_unit * res.product_qty
  27. farm = fields.Many2one(comodel_name='stock.location', string='Farm',
  28. domain=[('usage', '=', 'view')])
  29. location_id = fields.Many2one(comodel_name='stock.location', string='Yard',
  30. domain=[('usage', '!=', 'view'),
  31. ('silo', '!=', True)])
  32. start_date = fields.Datetime(string='Bill start day')
  33. end_date = fields.Datetime(string='Bill end date')
  34. general_expense = fields.Boolean(string='General Expense', default=False)
  35. imputed = fields.Boolean(string='Inputed', compute='_get_imputed',
  36. store=True)
  37. aux_price_subtotal = fields.Float(string='amount_untaxed',
  38. compute='_get_aux_price_subtotal',
  39. store=True)
  40. @api.multi
  41. def impute_purchase_order(self):
  42. for res in self:
  43. res.order_id.set_purchase_analitics()
  44. class Purchase_analitic_remain(models.Model):
  45. _name = 'purchase.analitic.remain'
  46. farm = fields.Many2one(comodel_name='stock.location', string='Farm')
  47. quantity = fields.Float(string='amount')
  48. last_calc = fields.Date(string='last day unit calc')
  49. quantity_per_unit = fields.Float(string='quantity per unit')
  50. class PurchaseOrder(models.Model):
  51. _inherit = 'purchase.order'
  52. imputed = fields.Boolean(string='Imputed', default=False)
  53. @api.multi
  54. def wkf_approve_order(self):
  55. super(PurchaseOrder, self).wkf_approve_order()
  56. for res in self:
  57. for line in res.order_line:
  58. if line.start_date:
  59. end_date = datetime.strptime(line.end_date, DFORMAT)
  60. start_date = datetime.strptime(line.start_date, DFORMAT)
  61. pass_days = (end_date - start_date).days
  62. if pass_days < 1 or not end_date or not start_date:
  63. raise Warning(
  64. _('the bill should have lower starting date '
  65. 'to the final'))
  66. if line.general_expense:
  67. raise Warning(
  68. _("General expenses can't aprove"))
  69. if line.farm and line.location_id:
  70. raise Warning(_('Choose farm or yard'))
  71. print res.state
  72. return True
  73. @api.multi
  74. def set_purchase_analitics(self):
  75. for res in self:
  76. if not res.imputed:
  77. res.imputed = True
  78. control = res.order_line[0].general_expense
  79. for line in res.order_line:
  80. if line.start_date and line.end_date:
  81. end_date = datetime.strptime(
  82. line.end_date, DFORMAT).date()
  83. if end_date >= datetime.today().date():
  84. raise Warning(
  85. _('Final date after current date'))
  86. if control and res.state != 'done':
  87. res.wkf_po_done()
  88. if line.farm and line.farm.factory:
  89. self.set_factory_cost(line)
  90. else:
  91. start_date = \
  92. datetime.strptime(
  93. line.start_date, DFORMAT).date()
  94. num_days = (end_date - start_date).days+1
  95. if line.farm and line.farm.transport:
  96. self.set_transport_cost(line)
  97. elif line.farm or line.location_id:
  98. afected_animals = self.get_party_per_day(
  99. start_date, end_date, line.farm,
  100. line.location_id)
  101. if len(afected_animals[0]) == 0 \
  102. and len(afected_animals[1]) == 0:
  103. analitic_remain_ob = \
  104. self.env['purchase.analitic.remain']
  105. if line.farm:
  106. farm = line.farm
  107. else:
  108. farm = self.get_farm(line.location_id)
  109. analitic_remain = \
  110. analitic_remain_ob.search([
  111. ('farm', '=', farm.id)])
  112. amount = line.price_unit * line.product_qty
  113. if len(analitic_remain) == 0:
  114. analitic_remain_ob.create({
  115. 'farm': farm.id,
  116. 'quantity': amount})
  117. else:
  118. analitic_remain.quantity = \
  119. analitic_remain.quantity + amount
  120. else:
  121. thread.start_new_thread(
  122. self.set_analytics, (
  123. afected_animals, line,
  124. num_days, line.farm))
  125. @api.multi
  126. def set_transport_cost(self, line):
  127. pick_type_obj = self.env['stock.picking.type']
  128. int_pick_type = pick_type_obj.search([
  129. ('code', '=', 'internal')])
  130. out_pick_type = pick_type_obj.search([
  131. ('code', '=', 'outgoing')])
  132. int_pick_type_ids = []
  133. for pick_t in int_pick_type:
  134. int_pick_type_ids.append(pick_t.id)
  135. out_pick_type_ids = []
  136. for pick_t in out_pick_type:
  137. out_pick_type_ids.append(pick_t.id)
  138. picking_obj = self.env['stock.picking']
  139. internal_trasports = picking_obj.search([
  140. ('picking_type_id', 'in', int_pick_type_ids),
  141. ('date_done', '<=', line.end_date),
  142. ('date_done', '>=', line.start_date)])
  143. out_trasports = picking_obj.search([
  144. ('picking_type_id', 'in', out_pick_type_ids),
  145. ('date_done', '<=', line.end_date),
  146. ('date_done', '>=', line.start_date)])
  147. tot_rel_trans = 0
  148. for transport in internal_trasports:
  149. dest_loc = transport.move_lines[0].location_dest_id
  150. warehouse = dest_loc.get_farm_warehouse()
  151. tot_rel_trans = tot_rel_trans + warehouse.radius
  152. for transport in out_trasports:
  153. warehouse = transport.picking_type_id.warehouse_id
  154. tot_rel_trans = tot_rel_trans + warehouse.radius
  155. cost_per_transport = (line.price_unit * line.product_qty)/tot_rel_trans
  156. self.set_internal_trasport(internal_trasports, cost_per_transport,)
  157. self.set_out_transport(out_trasports, cost_per_transport)
  158. @api.multi
  159. def set_internal_trasport(self, transports, cost_per_trans):
  160. animal_obj = self.env['farm.animal']
  161. an_group_obj = self.env['farm.animal.group']
  162. for transport in transports:
  163. dest_loc = transport.move_lines[0].location_dest_id
  164. if dest_loc.silo:
  165. dest_loc = dest_loc.locations_to_fed[0].location
  166. warehouse = dest_loc.get_farm_warehouse()
  167. animals = animal_obj.search([
  168. ('location', '=', dest_loc.id)])
  169. groups = an_group_obj.search([
  170. ('location', '=', dest_loc.id),
  171. ('state', '!=', 'sold')])
  172. tot_animals = len(animals)
  173. for group in groups:
  174. tot_animals = tot_animals + group.quantity
  175. tot_cost = cost_per_trans * warehouse.radius
  176. cost_per_animal = tot_cost/tot_animals
  177. for animal in animals:
  178. self.set_animal_cost(animal, transport.date_done,
  179. cost_per_animal, self.env)
  180. def set_out_transport(self, transports, cost_per_trans):
  181. an_group_obj = self.env['farm.animal.group']
  182. for transport in transports:
  183. warehouse = transport.pickng_type_id.warehouse_id
  184. groups = an_group_obj.search([
  185. ('location', '=', warehouse.view_location_id.id),
  186. ('state', '!=', 'sold')])
  187. tot_animals = 0
  188. for group in groups:
  189. tot_animals = tot_animals + group.quantity
  190. tot_cost = cost_per_trans * warehouse.radius
  191. cost_per_animal = tot_cost/tot_animals
  192. for group in groups:
  193. self.set_party_cost(group, transport.date_done,
  194. cost_per_animal)
  195. def set_factory_cost_old(self, line):
  196. analitic_remain_ob = self.env['purchase.analitic.remain']
  197. analitic_remain = analitic_remain_ob.search([
  198. ('farm', '=', line.farm.id)])
  199. amount = self.get_unit_price(line) * line.product_qty
  200. if len(analitic_remain) == 0:
  201. analitic_remain_ob.create({
  202. 'farm': line.farm.id,
  203. 'quantity': amount})
  204. else:
  205. analitic_remain.quantity = \
  206. analitic_remain.quantity + amount
  207. def set_factory_cost(self, line):
  208. new_cr = sql_db.db_connect(
  209. self.env.cr.dbname).cursor()
  210. uid, context = \
  211. self.env.uid, self.env.context
  212. with api.Environment.manage():
  213. new_env = api.Environment(
  214. new_cr, uid, context)
  215. productions = new_env['mrp.production'].search(
  216. [('date_finished', '>=', line.start_date),
  217. ('date_finished', '<', line.end_date)])
  218. moves = []
  219. afected_lots = []
  220. total_feed = 0
  221. for production in productions:
  222. total_feed = total_feed + production.product_qty
  223. for mov in production.move_created_ids2:
  224. moves.append(mov)
  225. amount = self.get_unit_price(line, new_env) * line.product_qty
  226. cost_per_kg = amount/total_feed
  227. for mov in moves:
  228. for quant in mov.quant_ids:
  229. lot = quant.lot_id
  230. if lot.id not in afected_lots:
  231. afected_lots.append(lot.id)
  232. lot.unit_cost = lot.unit_cost + cost_per_kg
  233. self.set_afected_feed_events(lot, cost_per_kg, line,
  234. new_env)
  235. new_env.cr.commit()
  236. new_cr.close()
  237. return True
  238. def set_afected_feed_events(self, lot, cost_kg, line,new_env):
  239. feed_events = new_env['farm.feed.event'].search(
  240. [('state', '=', 'validated'),
  241. ('feed_lot', '=', lot.id),
  242. ('animal_type', '=', 'group')])
  243. journal = new_env['account.analytic.journal'].with_context(
  244. {}).search([('code', '=', 'PUR')])
  245. analytic_line_obj = new_env['account.analytic.line']
  246. for event in feed_events:
  247. company = new_env['res.company'].with_context({}).search([
  248. ('id', '=', 1)])
  249. amount = event.feed_quantity * cost_kg
  250. analytic_line_obj.create({
  251. 'name': 'mrp-cost-' + self.name,
  252. 'date': line.start_date,
  253. 'amount': -amount,
  254. 'unit_amount': 1,
  255. 'account_id': event.animal_group.account.id,
  256. 'general_account_id': company.feed_account.id,
  257. 'journal_id': journal.id,
  258. })
  259. def get_farm(self, location):
  260. while(location.location_id.id != 1):
  261. location = location.location_id
  262. return location
  263. @api.model
  264. def get_party_per_day(self, start_date, end_date, farm, location_id):
  265. pass_days = (end_date-start_date).days
  266. if pass_days < 1 or not end_date or not start_date:
  267. raise Warning(
  268. _('the bill should have lower starting date '
  269. 'to the final'))
  270. ani_groups_obj = self.env['farm.animal.group'].with_context({}).search(
  271. [('id', '!=', False)])
  272. ani_obj = self.env['farm.animal'].with_context({}).search(
  273. [('id', '!=', False)])
  274. party_per_day = {}
  275. animal_per_day = {}
  276. current_day = end_date
  277. while current_day >= start_date:
  278. party_per_day[current_day] = []
  279. animal_per_day[current_day] = []
  280. current_day = current_day - timedelta(days=1)
  281. for party in ani_groups_obj:
  282. transition_location = []
  283. for loc in party.specie.lost_found_location:
  284. transition_location.append(loc.location.id)
  285. transition = self.env['farm.transformation.event'].with_context(
  286. {}).search([('animal_group', '=', party.id),
  287. ('from_location.id', 'in', transition_location),
  288. ('to_location.id', 'not in', transition_location)])
  289. if party.state == 'sold':
  290. sale_move = self.env['farm.move.event'].with_context(
  291. {}).search([('animal_group', '=', party.id)])
  292. sale_day = datetime.strptime(
  293. sale_move[-1].timestamp, DFORMAT).date()
  294. if len(transition) < 1:
  295. transition_finish = datetime.strptime(party.arrival_date,
  296. DAFORTMAT).date()
  297. else:
  298. transition_finish = datetime.strptime(transition.timestamp,
  299. DFORMAT).date()
  300. if farm:
  301. condition1 = party.farm == farm
  302. condition2 = self.get_farm(party.initial_location) == farm
  303. else:
  304. condition1 = sale_move[-1].from_location == location_id
  305. condition2 = party.initial_location == location_id
  306. if (start_date - sale_day).days < 0 and condition1:
  307. if end_date < sale_day:
  308. current_day = end_date
  309. else:
  310. current_day = sale_day
  311. control_date = self.get_control_date(party, start_date,
  312. transition_finish)
  313. self.add_party(
  314. party_per_day, party, current_day, control_date)
  315. elif condition2 and (transition_finish - start_date).days > 0:
  316. control_date = self.get_control_date2(party, start_date)
  317. if end_date < transition_finish:
  318. current_day = end_date
  319. else:
  320. current_day = transition_finish
  321. self.add_party(party_per_day, party, current_day,
  322. control_date)
  323. elif party.state == 'fatten':
  324. if farm:
  325. condition1 = party.farm == farm
  326. condition2 = self.get_farm(party.initial_location) == farm
  327. else:
  328. condition1 = party.location == location_id
  329. condition2 = party.initial_location == location_id
  330. if len(transition) < 1:
  331. transition_finish = datetime.strptime(party.arrival_date,
  332. "%Y-%m-%d").date()
  333. else:
  334. t_f = None
  335. for tran in transition:
  336. if t_f is None or t_f < tran.timestamp:
  337. t_f = tran.timestamp
  338. transition_finish = datetime.strptime(t_f,
  339. DFORMAT).date()
  340. if condition1:
  341. control_date = self.get_control_date(party, start_date,
  342. transition_finish)
  343. self.add_party(
  344. party_per_day, party, end_date, control_date)
  345. elif condition2 and \
  346. (transition_finish - start_date).days > 0:
  347. control_date = self.get_control_date2(party, start_date)
  348. if end_date < transition_finish:
  349. current_day = end_date
  350. else:
  351. current_day = transition_finish
  352. self.add_party(party_per_day, party, current_day,
  353. control_date)
  354. else:
  355. if farm:
  356. condition = party.farm == farm
  357. else:
  358. condition = party.location == location_id
  359. if condition:
  360. control_date = self.get_control_date2(party, start_date)
  361. self.add_party(
  362. party_per_day, party, end_date, control_date)
  363. for animal in ani_obj:
  364. if farm:
  365. condition = animal.farm == farm
  366. else:
  367. condition = animal.location == location_id
  368. if condition:
  369. control_date = self.get_control_date2(animal, start_date)
  370. self.add_party(animal_per_day, animal, end_date, control_date)
  371. return [party_per_day, animal_per_day]
  372. def add_party(self, party_per_day, party, current_day, control_date):
  373. while current_day >= control_date:
  374. party_per_day[current_day].append(party)
  375. current_day = current_day - timedelta(days=1)
  376. def get_control_date(self, party, start_date, transition_finish):
  377. if (start_date - transition_finish).days < 1:
  378. return transition_finish
  379. else:
  380. return start_date
  381. def get_control_date2(self, party, start_date):
  382. arrival_date = datetime.strptime(party.arrival_date, '%Y-%m-%d').date()
  383. if (arrival_date - start_date).days < 0:
  384. return start_date
  385. else:
  386. return arrival_date
  387. def set_analytics(self, afected_animals, line, num_days, farm):
  388. new_cr = sql_db.db_connect(
  389. self.env.cr.dbname).cursor()
  390. uid, context = \
  391. self.env.uid, self.env.context
  392. with api.Environment.manage():
  393. new_env = api.Environment(
  394. new_cr, uid, context)
  395. cost_per_day = (self.get_unit_price(line, new_env) * line.product_qty)/num_days
  396. animals = {}
  397. for d, partys in afected_animals[0].iteritems():
  398. remain = 0
  399. num_animals = len(afected_animals[1][d])
  400. for party in partys:
  401. num_animals += party.quantity
  402. if farm:
  403. analitic_remain_ob = new_env['purchase.analitic.remain']
  404. analitic_remain = analitic_remain_ob.search([
  405. ('farm', '=', farm.id)])
  406. if num_animals == 0:
  407. cost_per_animal_day = 0
  408. if len(analitic_remain) == 0:
  409. analitic_remain_ob.create({
  410. 'farm': farm.id,
  411. 'quantity': cost_per_day})
  412. else:
  413. analitic_remain.quantity = analitic_remain.quantity\
  414. + cost_per_day
  415. else:
  416. if farm and len(analitic_remain) != 0:
  417. remain = analitic_remain.quantity
  418. analitic_remain.unlink()
  419. cost_per_animal_day = (cost_per_day + remain) / num_animals
  420. for party in partys:
  421. self.set_party_cost(party, d, cost_per_animal_day, new_env)
  422. for animal in afected_animals[1][d]:
  423. if animal.id in animals:
  424. animals[animal.id][1] = \
  425. animals[animal.id][1] + cost_per_animal_day
  426. else:
  427. animals[animal.id] = [animal, cost_per_animal_day, d]
  428. for key in animals.iterkeys():
  429. an = animals[key]
  430. new_env['purchase.order'].set_animal_cost(an[0], an[2], an[1], new_env)
  431. num_days -= num_days-1
  432. new_env.cr.commit()
  433. new_cr.close()
  434. return True
  435. @api.one
  436. def set_party_cost(self, party, date, cost_per_animal_day, new_env):
  437. company = new_env['res.company'].with_context({}).search([
  438. ('id', '=', 1)])
  439. journal = new_env['account.analytic.journal'].with_context(
  440. {}).search([('code', '=', 'PUR')])
  441. analytic_line_obj = new_env['account.analytic.line']
  442. analytic_line_obj.create({
  443. 'name': self.name,
  444. 'date': date,
  445. 'amount': -(cost_per_animal_day * party.quantity),
  446. 'unit_amount': party.quantity,
  447. 'account_id': party.account.id,
  448. 'general_account_id': company.feed_account.id,
  449. 'journal_id': journal.id,
  450. })
  451. @api.one
  452. def set_animal_cost(self, animal, date, cost_per_animal_day, new_env):
  453. company = new_env['res.company'].with_context({}).search([
  454. ('id', '=', animal.farm.company_id.id)])
  455. journal = new_env['account.analytic.journal'].with_context(
  456. {}).search([('code', '=', 'PUR')])
  457. analytic_line_obj = new_env['account.analytic.line']
  458. analytic_line_obj.create({
  459. 'name': self.name,
  460. 'date': date,
  461. 'amount': -cost_per_animal_day,
  462. 'unit_amount': 1,
  463. 'account_id': animal.account.id,
  464. 'general_account_id': company.feed_account.id,
  465. 'journal_id': journal.id,
  466. })
  467. @api.multi
  468. def get_unit_price(self, line, new_env):
  469. invoice_line = new_env['account.invoice.line'].search(
  470. [('purchase_line_id', '=', line.id)])
  471. if invoice_line:
  472. return invoice_line[0].price_unit
  473. else:
  474. return line.price_unit