Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion hr_personal_equipment_stock/README.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

===========================
Hr Personal Equipment Stock
===========================
Expand All @@ -13,7 +17,7 @@ Hr Personal Equipment Stock
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr-lightgray.png?logo=github
Expand Down
58 changes: 52 additions & 6 deletions hr_personal_equipment_stock/models/hr_personal_equipment.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class HrPersonalEquipment(models.Model):
"stock.move", "personal_equipment_id", string="Stock Moves"
)
skip_procurement = fields.Boolean(compute="_compute_skip_procurement")
lot_ids = fields.Many2many(
"stock.lot", compute="_compute_lot_ids", string="Serial Numbers"
)

@api.depends("state", "product_id", "product_id.type")
def _compute_skip_procurement(self):
Expand All @@ -41,18 +44,61 @@ def _compute_skip_procurement(self):
"move_ids.scrapped",
"move_ids.product_uom_qty",
"move_ids.product_uom",
"equipment_request_id.picking_ids.move_ids.scrapped",
"equipment_request_id.picking_ids.move_ids.state",
)
def _compute_qty_delivered(self):
for line in self:
qty = 0.0
for move in line.move_ids.filtered(
lambda r: r.state == "done" and line.product_id == r.product_id
):
qty += move.product_uom._compute_quantity(
move.product_uom_qty, line.product_uom_id
)
dest_location = line.location_id
moves = line.move_ids.filtered(
lambda move: move.state == "done" and move.product_id == line.product_id
) + line.equipment_request_id.picking_ids.move_ids.filtered(
lambda move: move.scrapped
and move.state == "done"
and move.product_id == line.product_id
)
for move in moves:
moved_qty = move.quantity_done
if move.location_dest_id == dest_location:
qty += moved_qty
elif move.location_id == dest_location:
qty -= moved_qty
line.qty_delivered = qty

@api.depends(
"move_ids.lot_ids",
"equipment_request_id.picking_ids.move_ids.scrapped",
"equipment_request_id.picking_ids.move_ids.state",
)
def _compute_lot_ids(self):
for line in self:
qty_by_lot = {}
dest_location = line.location_id
moves = line.move_ids.filtered(
lambda move: move.state == "done" and move.product_id == line.product_id
) + line.equipment_request_id.picking_ids.move_ids.filtered(
lambda move: move.scrapped
and move.state == "done"
and move.product_id == line.product_id
)
for move in moves:
for move_line in move.move_line_ids:
lot = move_line.lot_id
if lot not in qty_by_lot:
qty_by_lot[lot] = 0
moved_qty = move_line.qty_done
if move.location_dest_id == dest_location:
qty_by_lot[lot] += moved_qty
elif move.location_id == dest_location:
qty_by_lot[lot] -= moved_qty

lots = self.env["stock.lot"].browse()
for lot, moved in qty_by_lot.items():
if moved > 0:
lots |= lot
line.lot_ids = lots

def _skip_procurement(self):
return self.product_id.type not in ("consu", "product")

Expand Down
26 changes: 16 additions & 10 deletions hr_personal_equipment_stock/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Hr Personal Equipment Stock</title>
<title>README.rst</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -360,16 +360,21 @@
</style>
</head>
<body>
<div class="document" id="hr-personal-equipment-stock">
<h1 class="title">Hr Personal Equipment Stock</h1>
<div class="document">


<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="hr-personal-equipment-stock">
<h1>Hr Personal Equipment Stock</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:fc5b579fad57f746f95989e01349e102d240d01825132f2a8b935a5b2499ccfb
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/hr/tree/16.0/hr_personal_equipment_stock"><img alt="OCA/hr" src="https://img.shields.io/badge/github-OCA%2Fhr-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/hr-16-0/hr-16-0-hr_personal_equipment_stock"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/hr&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/hr/tree/16.0/hr_personal_equipment_stock"><img alt="OCA/hr" src="https://img.shields.io/badge/github-OCA%2Fhr-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/hr-16-0/hr-16-0-hr_personal_equipment_stock"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/hr&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module integrates stock with hr_personal_equipment_request module.
When the equipment request is accepted, a stock request is generated and a “stock.move” is created for each request line.
If the “stock.move” is marked as done, the corresponding allocations are marked as valid if the quantity_delivered is equal to the requested quantity.
Expand All @@ -389,33 +394,33 @@ <h1 class="title">Hr Personal Equipment Stock</h1>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#toc-entry-1">Installation</a></h1>
<h2><a class="toc-backref" href="#toc-entry-1">Installation</a></h2>
<p>This module is auto installed if the modules “hr_personal_equipment_request” and “stock” are installed.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/hr/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/hr/issues/new?body=module:%20hr_personal_equipment_stock%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<ul class="simple">
<li>Creu Blanca</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<ul class="simple">
<li>Alba Riera &lt;<a class="reference external" href="mailto:alba.riera&#64;creublanca.es">alba.riera&#64;creublanca.es</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand All @@ -428,5 +433,6 @@ <h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
</div>
</div>
</div>
</div>
</body>
</html>
129 changes: 128 additions & 1 deletion hr_personal_equipment_stock/tests/test_hr_personal_equipment_stock.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Copyright 2021 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from uuid import uuid4

from odoo.exceptions import UserError
from odoo.tests import TransactionCase
from odoo.tests import Form, TransactionCase


class TestHRPersonalEquipment(TransactionCase):
Expand Down Expand Up @@ -162,15 +164,132 @@ def test_skip_procurement(self):
self.personal_equipment_request.line_ids[1]._compute_skip_procurement()
self.assertTrue(self.personal_equipment_request.line_ids[1].skip_procurement)

def _create_lot_ids(self, product, quantity):
product.tracking = "serial"
lot_ids = []
for _qty in range(quantity):
serial_number = str(uuid4())
lot_id = self.env["stock.lot"].create(
{
"name": f"Lots for tests {product.name} - {serial_number}",
"product_id": product.product_variant_id.id,
}
)
lot_ids += lot_id
return lot_ids

def test_compute_qty_delivered(self):
allocation = self.personal_equipment_request.line_ids[0]
lot_ids = self._create_lot_ids(
self.product_personal_equipment_1, allocation.quantity
)
self.personal_equipment_request.accept_request()
move = allocation.move_ids[0]
move.lot_ids = [lot_id.id for lot_id in lot_ids]
move.quantity_done = allocation.quantity
picking = self.personal_equipment_request.picking_ids[0]
picking._action_done()
self.assertEqual(allocation.qty_delivered, allocation.quantity)
self.assertEqual(len(allocation.lot_ids), allocation.qty_delivered)
self.assertEqual(allocation.lot_ids, allocation.move_ids.lot_ids)
self.assertEqual(allocation.state, "valid")
# Return picking
return_form = Form(
self.env["stock.return.picking"].with_context(
active_id=picking.id, active_model="stock.picking"
)
)
return_wizard = return_form.save()
action = return_wizard.create_returns()
return_picking = self.env["stock.picking"].browse(action["res_id"])
return_picking.move_line_ids.write({"qty_done": 1})
return_picking.button_validate()
self.assertEqual(len(self.personal_equipment_request.picking_ids), 2)
self.assertEqual(allocation.qty_delivered, 0)
self.assertNotEqual(allocation.lot_ids, allocation.move_ids.lot_ids)
self.assertFalse(allocation.lot_ids)
# Duplicate picking
duplicate_picking = picking.copy()
lot_ids = self._create_lot_ids(
self.product_personal_equipment_1, allocation.quantity
)
move = duplicate_picking.move_ids
move.lot_ids = [lot_id.id for lot_id in lot_ids]
move.quantity_done = allocation.quantity
duplicate_picking._action_done()
self.assertEqual(allocation.qty_delivered, allocation.quantity)
self.assertEqual(len(allocation.lot_ids), allocation.qty_delivered)
self.assertEqual(allocation.lot_ids, duplicate_picking.move_ids.lot_ids)
self.assertEqual(allocation.state, "valid")
# create scraps
self.assertEqual(allocation.qty_delivered, 3)
scrap = self.env["stock.scrap"].create(
{
"product_id": allocation.product_id.id,
"scrap_qty": 1.0,
"location_id": self.location_employee.id,
"picking_id": duplicate_picking.id,
"lot_id": lot_ids[0].id,
}
)
scrap.action_validate()
self.assertEqual(scrap.state, "done")
self.assertTrue(duplicate_picking.has_scrap_move)
self.assertEqual(len(allocation.lot_ids), 2)
self.assertNotIn(lot_ids[0], allocation.lot_ids)
self.assertEqual(allocation.qty_delivered, 2)

def test_compute_qty_delivered_without_lot_ids(self):
allocation = self.personal_equipment_request.line_ids[0]
self.personal_equipment_request.accept_request()
move = allocation.move_ids[0]
move.quantity_done = allocation.quantity
picking = self.personal_equipment_request.picking_ids[0]
picking._action_done()
self.assertEqual(allocation.qty_delivered, allocation.quantity)
self.assertFalse(allocation.lot_ids)
self.assertEqual(allocation.lot_ids, allocation.move_ids.lot_ids)
self.assertEqual(allocation.state, "valid")
# Return picking
return_form = Form(
self.env["stock.return.picking"].with_context(
active_id=picking.id, active_model="stock.picking"
)
)
return_wizard = return_form.save()
action = return_wizard.create_returns()
return_picking = self.env["stock.picking"].browse(action["res_id"])
return_picking.move_line_ids.write({"qty_done": 3})
return_picking.button_validate()
self.assertEqual(len(self.personal_equipment_request.picking_ids), 2)
self.assertEqual(allocation.qty_delivered, 0)
self.assertEqual(allocation.lot_ids, allocation.move_ids.lot_ids)
self.assertFalse(allocation.lot_ids)
# Duplicate picking
duplicate_picking = picking.copy()
move = duplicate_picking.move_ids
move.quantity_done = allocation.quantity
duplicate_picking._action_done()
self.assertEqual(allocation.qty_delivered, allocation.quantity)
self.assertFalse(allocation.lot_ids)
self.assertEqual(allocation.lot_ids, duplicate_picking.move_ids.lot_ids)
self.assertEqual(allocation.state, "valid")
# create scraps
self.assertEqual(allocation.qty_delivered, 3)
scrap = self.env["stock.scrap"].create(
{
"product_id": allocation.product_id.id,
"scrap_qty": 1.0,
"location_id": self.location_employee.id,
"picking_id": duplicate_picking.id,
}
)
scrap.action_validate()
self.assertEqual(scrap.state, "done")
self.assertFalse(scrap.lot_id)
self.assertTrue(duplicate_picking.has_scrap_move)
self.assertFalse(allocation.lot_ids)
self.assertEqual(allocation.qty_delivered, 2)

def test_quantity_delivered_skip_procurement(self):
allocation = self.personal_equipment_request.line_ids[1]
Expand All @@ -188,24 +307,32 @@ def test_action_launch_procurement_rule_raise_error(self):

def test_action_cancel_with_qty_delivered(self):
allocation = self.personal_equipment_request.line_ids[0]
self._create_lot_ids(self.product_personal_equipment_1, allocation.quantity)
self.personal_equipment_request.accept_request()
self.assertEqual(allocation.state, "accepted")
picking = self.personal_equipment_request.picking_ids[0]
picking.action_cancel()
self.assertEqual(allocation.qty_delivered, 0)
self.assertFalse(allocation.lot_ids)
self.assertEqual(allocation.state, "cancelled")

def test_action_cancel_without_qty_delivered(self):
allocation = self.personal_equipment_request.line_ids[0]
lot_ids = self._create_lot_ids(
self.product_personal_equipment_1, allocation.quantity
)
self.personal_equipment_request.accept_request()
self.assertEqual(allocation.state, "accepted")
move = allocation.move_ids[0]
move.lot_ids = [lot_ids[index].id for index in range(allocation.quantity - 1)]
move.quantity_done = allocation.quantity - 1
picking = self.personal_equipment_request.picking_ids[0]
picking._action_done()
back_order = self.personal_equipment_request.picking_ids[1]
back_order.action_cancel()
self.assertEqual(allocation.qty_delivered, allocation.quantity - 1)
self.assertEqual(len(allocation.lot_ids), allocation.qty_delivered)
self.assertEqual(allocation.lot_ids, allocation.move_ids.lot_ids)
self.assertEqual(allocation.state, "valid")

def test_action_view_pickings(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
</xpath>
<xpath expr="//tree/field[@name='state']" position="before">
<field name="qty_delivered" />
<field
name="lot_ids"
widget="many2many_tags"
attrs="{'column_invisible': [('parent.state', '!=', 'accepted')]}"
/>
<field name="location_id" invisible="1" />
<field name="procurement_group_id" invisible="1" />
</xpath>
Expand Down
Loading