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.

198 lines
7.0 KiB

4 years ago
4 years ago
  1. # Copyright 2020 Coop IT Easy SCRL fs
  2. # Robin Keunen <robin@coopiteasy.be>
  3. # Vincent Van Rossem <vincent@coopiteasy.be>
  4. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
  5. import logging
  6. from odoo import _, api, fields, models
  7. from odoo.exceptions import ValidationError
  8. _logger = logging.getLogger(__name__)
  9. class PurchaseOrderGeneratorLine(models.Model):
  10. _description = "Purchase Order Generator Line"
  11. _name = "purchase.order.generator.line"
  12. name = fields.Char(string="Product Name", compute="_compute_name")
  13. cpo_id = fields.Many2one(
  14. comodel_name="purchase.order.generator",
  15. string="Purchase Order Generator",
  16. )
  17. product_template_id = fields.Many2one(
  18. comodel_name="product.template",
  19. string="Linked Product Template",
  20. required=True,
  21. help="Product",
  22. )
  23. purchase_quantity = fields.Float(string="Purchase Quantity", default=0.0)
  24. category_id = fields.Many2one(
  25. comodel_name="product.category",
  26. string="Internal Category",
  27. related="product_template_id.categ_id",
  28. read_only=True,
  29. )
  30. uom_id = fields.Many2one(
  31. comodel_name="uom.uom",
  32. string="Unit of Measure",
  33. read_only=True,
  34. related="product_template_id.uom_id",
  35. help="Default Unit of Measure used for all stock operation.",
  36. )
  37. qty_available = fields.Float(
  38. string="Stock Quantity",
  39. related="product_template_id.qty_available",
  40. read_only=True,
  41. help="Quantity currently in stock. Does not take "
  42. "into account incoming orders.",
  43. )
  44. virtual_available = fields.Float(
  45. string="Forecast Quantity",
  46. related="product_template_id.virtual_available",
  47. read_only=True,
  48. help="Virtual quantity taking into account current stock, incoming "
  49. "orders and outgoing sales.",
  50. )
  51. daily_sales = fields.Float(
  52. string="Average Consumption",
  53. related="product_template_id.daily_sales",
  54. read_only=True,
  55. )
  56. stock_coverage = fields.Float(
  57. string="Stock Coverage",
  58. related="product_template_id.stock_coverage",
  59. read_only=True,
  60. )
  61. uom_po_id = fields.Many2one(
  62. comodel_name="uom.uom",
  63. string="Purchase Unit of Measure",
  64. read_only=True,
  65. related="product_template_id.uom_po_id",
  66. help="Default Unit of Measure used for all stock operation.",
  67. )
  68. supplierinfo_id = fields.Many2one(
  69. comodel_name="product.supplierinfo",
  70. string="Supplier information",
  71. compute="_compute_supplierinfo",
  72. store=True,
  73. readonly=True,
  74. )
  75. minimum_purchase_qty = fields.Float(
  76. string="Minimum Purchase Quantity", related="supplierinfo_id.min_qty"
  77. )
  78. product_price = fields.Float(
  79. string="Product Price (w/o VAT)",
  80. related="supplierinfo_id.price",
  81. help="Supplier Product Price by buying unit. Price is without VAT",
  82. )
  83. virtual_coverage = fields.Float(
  84. string="Expected Stock Coverage",
  85. compute="_compute_coverage_and_subtotal",
  86. help="Expected stock coverage (in days) based on current stocks and "
  87. "average daily consumption",
  88. )
  89. subtotal = fields.Float(
  90. string="Subtotal (w/o VAT)", compute="_compute_coverage_and_subtotal"
  91. )
  92. @api.multi
  93. @api.depends("supplierinfo_id")
  94. def _compute_name(self):
  95. for cpol in self:
  96. if cpol.supplierinfo_id and cpol.supplierinfo_id.product_code:
  97. product_code = cpol.supplierinfo_id.product_code
  98. product_name = cpol.product_template_id.name
  99. cpol_name = "[{}] {}".format(product_code, product_name)
  100. else:
  101. cpol_name = cpol.product_template_id.name
  102. cpol.name = cpol_name
  103. @api.multi
  104. @api.onchange("product_template_id")
  105. def _onchange_purchase_quantity(self):
  106. for cpol in self:
  107. cpol.purchase_quantity = cpol.minimum_purchase_qty
  108. @api.onchange("product_template_id")
  109. def _onchange_product_template_id(self):
  110. """
  111. Change domain on product_template_id based on supplier given
  112. in the cpo.
  113. """
  114. default_supplier = self._context.get("cpo_seller_id")
  115. product_ids = []
  116. if default_supplier:
  117. product_ids = self.env["product.template"].search(
  118. [
  119. ("main_seller_id", "=", default_supplier),
  120. ("purchase_ok", "=", True),
  121. ]
  122. ).ids
  123. return {"domain": {"product_template_id": [("id", "in", product_ids)]}}
  124. @api.multi
  125. @api.depends("purchase_quantity")
  126. def _compute_coverage_and_subtotal(self):
  127. for cpol in self:
  128. cpol.subtotal = cpol.product_price * cpol.purchase_quantity
  129. avg = cpol.daily_sales
  130. if avg > 0:
  131. qty = (cpol.virtual_available / cpol.uom_id.factor) + (
  132. cpol.purchase_quantity / cpol.uom_po_id.factor
  133. )
  134. cpol.virtual_coverage = qty / avg
  135. else:
  136. # todo what would be a good default value? (not float(inf))
  137. cpol.virtual_coverage = 9999
  138. return True
  139. @api.multi
  140. @api.depends("product_template_id")
  141. def _compute_supplierinfo(self):
  142. for cpol in self:
  143. if not cpol.product_template_id:
  144. continue
  145. si = self.env["product.supplierinfo"].search(
  146. [
  147. ("product_tmpl_id", "=", cpol.product_template_id.id),
  148. ("name", "=", cpol.cpo_id.supplier_id.id),
  149. ]
  150. )
  151. if len(si) == 0:
  152. raise ValidationError(
  153. _("POG supplier does not sell product {name}").format(
  154. name=cpol.product_template_id.name
  155. )
  156. )
  157. elif len(si) > 1:
  158. _logger.warning(
  159. "product {name} has several supplier info set, chose last".format(
  160. name=cpol.product_template_id.name
  161. )
  162. )
  163. si = si.sorted(key=lambda r: r.create_date, reverse=True)
  164. cpol.supplierinfo_id = si[0]
  165. @api.constrains("purchase_quantity")
  166. def _check_minimum_purchase_quantity(self):
  167. for cpol in self:
  168. if cpol.purchase_quantity < 0:
  169. raise ValidationError(
  170. _(
  171. "Purchase quantity for {product_name} "
  172. "must be greater than 0"
  173. ).format(product_name=cpol.product_template_id.name)
  174. )
  175. elif 0 < cpol.purchase_quantity < cpol.minimum_purchase_qty:
  176. raise ValidationError(
  177. _(
  178. "Purchase quantity for {product_name} "
  179. "must be greater than {min_qty}"
  180. ).format(
  181. product_name=cpol.product_template_id.name,
  182. min_qty=cpol.minimum_purchase_qty,
  183. )
  184. )