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.

181 lines
6.4 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.multi
  109. @api.depends("purchase_quantity")
  110. def _compute_coverage_and_subtotal(self):
  111. for cpol in self:
  112. cpol.subtotal = cpol.product_price * cpol.purchase_quantity
  113. avg = cpol.daily_sales
  114. if avg > 0:
  115. qty = (cpol.virtual_available / cpol.uom_id.factor) + (
  116. cpol.purchase_quantity / cpol.uom_po_id.factor
  117. )
  118. cpol.virtual_coverage = qty / avg
  119. else:
  120. # todo what would be a good default value? (not float(inf))
  121. cpol.virtual_coverage = 9999
  122. return True
  123. @api.multi
  124. @api.depends("product_template_id")
  125. def _compute_supplierinfo(self):
  126. for cpol in self:
  127. if not cpol.product_template_id:
  128. continue
  129. si = self.env["product.supplierinfo"].search(
  130. [
  131. ("product_tmpl_id", "=", cpol.product_template_id.id),
  132. ("name", "=", cpol.cpo_id.supplier_id.id),
  133. ]
  134. )
  135. if len(si) == 0:
  136. raise ValidationError(
  137. _("POG supplier does not sell product {name}").format(
  138. name=cpol.product_template_id.name
  139. )
  140. )
  141. elif len(si) > 1:
  142. _logger.warning(
  143. "product {name} has several supplier info set, chose last".format(
  144. name=cpol.product_template_id.name
  145. )
  146. )
  147. si = si.sorted(key=lambda r: r.create_date, reverse=True)
  148. cpol.supplierinfo_id = si[0]
  149. @api.constrains("purchase_quantity")
  150. def _check_minimum_purchase_quantity(self):
  151. for cpol in self:
  152. if cpol.purchase_quantity < 0:
  153. raise ValidationError(
  154. _(
  155. "Purchase quantity for {product_name} "
  156. "must be greater than 0"
  157. ).format(product_name=cpol.product_template_id.name)
  158. )
  159. elif 0 < cpol.purchase_quantity < cpol.minimum_purchase_qty:
  160. raise ValidationError(
  161. _(
  162. "Purchase quantity for {product_name} "
  163. "must be greater than {min_qty}"
  164. ).format(
  165. product_name=cpol.product_template_id.name,
  166. min_qty=cpol.minimum_purchase_qty,
  167. )
  168. )