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.

195 lines
6.7 KiB

  1. import logging
  2. from odoo import models, fields, api
  3. from odoo.exceptions import ValidationError
  4. _logger = logging.getLogger(__name__)
  5. class ComputedPurchaseOrderLine(models.Model):
  6. _description = 'Computed Purchase Order Line'
  7. _name = 'computed.purchase.order.line'
  8. computed_purchase_order_id = fields.Many2one(
  9. 'computed.purchase.order',
  10. string='Computed Purchase Order',
  11. )
  12. product_template_id = fields.Many2one(
  13. 'product.template',
  14. string='Linked Product Template',
  15. required=True,
  16. help='Product')
  17. name = fields.Char(
  18. string='Product Name',
  19. related='product_template_id.name',
  20. read_only=True)
  21. supplierinfo_id = fields.Many2one(
  22. 'product.supplierinfo',
  23. string='Supplier information',
  24. compute='_compute_supplierinfo',
  25. store=True,
  26. readonly=True,
  27. )
  28. category_id = fields.Many2one(
  29. 'product.category',
  30. string='Internal Category',
  31. related='product_template_id.categ_id',
  32. read_only=True)
  33. uom_id = fields.Many2one(
  34. 'uom.uom',
  35. string='Unit of Measure',
  36. read_only=True,
  37. related='product_template_id.uom_id',
  38. help="Default Unit of Measure used for all stock operation.")
  39. qty_available = fields.Float(
  40. string='Stock Quantity',
  41. related='product_template_id.qty_available',
  42. read_only=True,
  43. help='Quantity currently in stock. Does not take '
  44. 'into account incoming orders.')
  45. virtual_available = fields.Float(
  46. string='Forecast Quantity',
  47. related='product_template_id.virtual_available',
  48. read_only=True,
  49. help='Virtual quantity taking into account current stock, incoming '
  50. 'orders and outgoing sales.')
  51. daily_sales = fields.Float(
  52. string='Average Consumption',
  53. related='product_template_id.daily_sales',
  54. read_only=True)
  55. stock_coverage = fields.Float(
  56. string='Stock Coverage',
  57. related='product_template_id.stock_coverage',
  58. read_only=True,
  59. )
  60. minimum_purchase_qty = fields.Float(
  61. string='Minimum Purchase Quantity',
  62. compute='_depends_on_product_template',
  63. )
  64. purchase_quantity = fields.Float(
  65. string='Purchase Quantity',
  66. required=True,
  67. default=0.)
  68. uom_po_id = fields.Many2one(
  69. 'uom.uom',
  70. string='Purchase Unit of Measure',
  71. read_only=True,
  72. related='product_template_id.uom_po_id',
  73. help="Default Unit of Measure used for all stock operation.")
  74. product_price = fields.Float(
  75. string='Product Price (w/o VAT)',
  76. compute='_depends_on_product_template',
  77. read_only=True,
  78. help='Supplier Product Price by buying unit. Price is without VAT')
  79. virtual_coverage = fields.Float(
  80. string='Expected Stock Coverage',
  81. compute='_depends_on_purchase_quantity',
  82. help='Expected stock coverage (in days) based on current stocks and average daily consumption') # noqa
  83. subtotal = fields.Float(
  84. string='Subtotal (w/o VAT)',
  85. compute='_depends_on_purchase_quantity')
  86. @api.multi
  87. @api.depends('product_template_id')
  88. def _depends_on_product_template(self):
  89. for cpol in self:
  90. # get supplier info
  91. cpol.minimum_purchase_qty = cpol.supplierinfo_id.min_qty
  92. cpol.product_price = cpol.supplierinfo_id.price
  93. @api.multi
  94. @api.onchange('product_template_id')
  95. def _onchange_purchase_quantity(self):
  96. for cpol in self:
  97. cpol.purchase_quantity = cpol.supplierinfo_id.min_qty
  98. @api.depends('purchase_quantity')
  99. @api.multi
  100. def _depends_on_purchase_quantity(self):
  101. for cpol in self:
  102. cpol.subtotal = cpol.product_price * cpol.purchase_quantity
  103. avg = cpol.daily_sales
  104. if avg > 0:
  105. qty = ((cpol.virtual_available / cpol.uom_id.factor)
  106. + (cpol.purchase_quantity / cpol.uom_po_id.factor))
  107. cpol.virtual_coverage = qty / avg
  108. else:
  109. # todo what would be a good default value? (not float(inf))
  110. cpol.virtual_coverage = 9999
  111. return True
  112. @api.multi
  113. @api.depends('product_template_id')
  114. @api.onchange('product_template_id')
  115. def _compute_supplierinfo(self):
  116. for cpol in self:
  117. if not cpol.product_template_id:
  118. cpol.supplierinfo_id = False
  119. else:
  120. SupplierInfo = self.env['product.supplierinfo']
  121. si = SupplierInfo.search([
  122. ('product_tmpl_id', '=', cpol.product_template_id.id),
  123. ('name', '=', cpol.product_template_id.main_supplier_id.id) # noqa
  124. ])
  125. if len(si) == 0:
  126. raise ValidationError(
  127. 'No supplier information set for {name}'
  128. .format(name=cpol.product_template_id.name))
  129. elif len(si) == 1:
  130. cpol.supplierinfo_id = si
  131. else:
  132. _logger.warning(
  133. 'product {name} has several suppliers, chose last'
  134. .format(name=cpol.product_template_id.name)
  135. )
  136. si = si.sorted(key=lambda r: r.create_date, reverse=True)
  137. cpol.supplierinfo_id = si[0]
  138. @api.constrains('purchase_quantity')
  139. def _check_minimum_purchase_quantity(self):
  140. for cpol in self:
  141. if cpol.purchase_quantity < 0:
  142. raise ValidationError('Purchase quantity for {product_name} must be greater than 0' # noqa
  143. .format(product_name=cpol.product_template_id.name))
  144. elif 0 < cpol.purchase_quantity < cpol.minimum_purchase_qty:
  145. raise ValidationError('Purchase quantity for {product_name} must be greater than {min_qty}' # noqa
  146. .format(product_name=cpol.product_template_id.name,
  147. min_qty=cpol.minimum_purchase_qty))
  148. @api.multi
  149. def get_default_product_product(self):
  150. self.ensure_one()
  151. ProductProduct = self.env['product.product']
  152. products = ProductProduct.search([
  153. ('product_tmpl_id', '=', self.product_template_id.id)
  154. ])
  155. products = products.sorted(
  156. key=lambda product: product.create_date,
  157. reverse=True
  158. )
  159. if products:
  160. return products[0]
  161. else:
  162. raise ValidationError(
  163. '%s:%s template has no variant set'
  164. % (self.product_template_id.id, self.product_template_id.name)
  165. )