mirror of
https://github.com/bringout/oca-ocb-mrp.git
synced 2026-04-24 01:51:59 +02:00
Initial commit: Mrp packages
This commit is contained in:
commit
50d736b3bd
739 changed files with 538193 additions and 0 deletions
47
odoo-bringout-oca-ocb-mrp_repair/README.md
Normal file
47
odoo-bringout-oca-ocb-mrp_repair/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Mrp Repairs
|
||||
|
||||
Odoo addon: mrp_repair
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-ocb-mrp_repair
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- repair
|
||||
- mrp
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Mrp Repairs
|
||||
- **Version**: 1.0
|
||||
- **Category**: Inventory/Inventory
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `mrp_repair`.
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
32
odoo-bringout-oca-ocb-mrp_repair/doc/ARCHITECTURE.md
Normal file
32
odoo-bringout-oca-ocb-mrp_repair/doc/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U[Users] -->|HTTP| V[Views and QWeb Templates]
|
||||
V --> C[Controllers]
|
||||
V --> W[Wizards – Transient Models]
|
||||
C --> M[Models and ORM]
|
||||
W --> M
|
||||
M --> R[Reports]
|
||||
DX[Data XML] --> M
|
||||
S[Security – ACLs and Groups] -. enforces .-> M
|
||||
|
||||
subgraph Mrp_repair Module - mrp_repair
|
||||
direction LR
|
||||
M:::layer
|
||||
W:::layer
|
||||
C:::layer
|
||||
V:::layer
|
||||
R:::layer
|
||||
S:::layer
|
||||
DX:::layer
|
||||
end
|
||||
|
||||
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
|
||||
```
|
||||
|
||||
Notes
|
||||
- Views include tree/form/kanban templates and report templates.
|
||||
- Controllers provide website/portal routes when present.
|
||||
- Wizards are UI flows implemented with `models.TransientModel`.
|
||||
- Data XML loads data/demo records; Security defines groups and access.
|
||||
3
odoo-bringout-oca-ocb-mrp_repair/doc/CONFIGURATION.md
Normal file
3
odoo-bringout-oca-ocb-mrp_repair/doc/CONFIGURATION.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for mrp_repair. Configure related models, access rights, and options as needed.
|
||||
3
odoo-bringout-oca-ocb-mrp_repair/doc/CONTROLLERS.md
Normal file
3
odoo-bringout-oca-ocb-mrp_repair/doc/CONTROLLERS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
6
odoo-bringout-oca-ocb-mrp_repair/doc/DEPENDENCIES.md
Normal file
6
odoo-bringout-oca-ocb-mrp_repair/doc/DEPENDENCIES.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
- [repair](../../odoo-bringout-oca-ocb-repair)
|
||||
- [mrp](../../odoo-bringout-oca-ocb-mrp)
|
||||
4
odoo-bringout-oca-ocb-mrp_repair/doc/FAQ.md
Normal file
4
odoo-bringout-oca-ocb-mrp_repair/doc/FAQ.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# FAQ
|
||||
|
||||
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
|
||||
- Q: How to enable? A: Start server with --addon mrp_repair or install in UI.
|
||||
7
odoo-bringout-oca-ocb-mrp_repair/doc/INSTALL.md
Normal file
7
odoo-bringout-oca-ocb-mrp_repair/doc/INSTALL.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-ocb-mrp_repair"
|
||||
# or
|
||||
uv pip install odoo-bringout-oca-ocb-mrp_repair"
|
||||
```
|
||||
13
odoo-bringout-oca-ocb-mrp_repair/doc/MODELS.md
Normal file
13
odoo-bringout-oca-ocb-mrp_repair/doc/MODELS.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Models
|
||||
|
||||
Detected core models and extensions in mrp_repair.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class repair_line
|
||||
class repair_order
|
||||
```
|
||||
|
||||
Notes
|
||||
- Classes show model technical names; fields omitted for brevity.
|
||||
- Items listed under _inherit are extensions of existing models.
|
||||
6
odoo-bringout-oca-ocb-mrp_repair/doc/OVERVIEW.md
Normal file
6
odoo-bringout-oca-ocb-mrp_repair/doc/OVERVIEW.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Overview
|
||||
|
||||
Packaged Odoo addon: mrp_repair. Provides features documented in upstream Odoo 16 under this addon.
|
||||
|
||||
- Source: OCA/OCB 16.0, addon mrp_repair
|
||||
- License: LGPL-3
|
||||
3
odoo-bringout-oca-ocb-mrp_repair/doc/REPORTS.md
Normal file
3
odoo-bringout-oca-ocb-mrp_repair/doc/REPORTS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
8
odoo-bringout-oca-ocb-mrp_repair/doc/SECURITY.md
Normal file
8
odoo-bringout-oca-ocb-mrp_repair/doc/SECURITY.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Security
|
||||
|
||||
This module does not define custom security rules or access controls beyond Odoo defaults.
|
||||
|
||||
Default Odoo security applies:
|
||||
- Base user access through standard groups
|
||||
- Model access inherited from dependencies
|
||||
- No custom row-level security rules
|
||||
5
odoo-bringout-oca-ocb-mrp_repair/doc/TROUBLESHOOTING.md
Normal file
5
odoo-bringout-oca-ocb-mrp_repair/doc/TROUBLESHOOTING.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Troubleshooting
|
||||
|
||||
- Ensure Python and Odoo environment matches repo guidance.
|
||||
- Check database connectivity and logs if startup fails.
|
||||
- Validate that dependent addons listed in DEPENDENCIES.md are installed.
|
||||
7
odoo-bringout-oca-ocb-mrp_repair/doc/USAGE.md
Normal file
7
odoo-bringout-oca-ocb-mrp_repair/doc/USAGE.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Usage
|
||||
|
||||
Start Odoo including this addon (from repo root):
|
||||
|
||||
```bash
|
||||
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon mrp_repair
|
||||
```
|
||||
3
odoo-bringout-oca-ocb-mrp_repair/doc/WIZARDS.md
Normal file
3
odoo-bringout-oca-ocb-mrp_repair/doc/WIZARDS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Wizards
|
||||
|
||||
This module does not include UI wizards.
|
||||
4
odoo-bringout-oca-ocb-mrp_repair/mrp_repair/__init__.py
Normal file
4
odoo-bringout-oca-ocb-mrp_repair/mrp_repair/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
13
odoo-bringout-oca-ocb-mrp_repair/mrp_repair/__manifest__.py
Normal file
13
odoo-bringout-oca-ocb-mrp_repair/mrp_repair/__manifest__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
{
|
||||
'name': 'Mrp Repairs',
|
||||
'version': '1.0',
|
||||
'category': 'Inventory/Inventory',
|
||||
'depends': ['repair', 'mrp'],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
26
odoo-bringout-oca-ocb-mrp_repair/mrp_repair/i18n/bs.po
Normal file
26
odoo-bringout-oca-ocb-mrp_repair/mrp_repair/i18n/bs.po
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mrp_repair
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-02-06 13:32+0000\n"
|
||||
"PO-Revision-Date: 2024-02-06 13:32+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: mrp_repair
|
||||
#: model:ir.model,name:mrp_repair.model_repair_line
|
||||
msgid "Repair Line (parts)"
|
||||
msgstr "[PREVOD: Repair Line (parts)]"
|
||||
|
||||
#. module: mrp_repair
|
||||
#: model:ir.model,name:mrp_repair.model_repair_order
|
||||
msgid "Repair Order"
|
||||
msgstr "Nalog za popravak"
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mrp_repair
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-02-06 13:32+0000\n"
|
||||
"PO-Revision-Date: 2024-02-06 13:32+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: mrp_repair
|
||||
#: model:ir.model,name:mrp_repair.model_repair_line
|
||||
msgid "Repair Line (parts)"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_repair
|
||||
#: model:ir.model,name:mrp_repair.model_repair_order
|
||||
msgid "Repair Order"
|
||||
msgstr ""
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import repair
|
||||
66
odoo-bringout-oca-ocb-mrp_repair/mrp_repair/models/repair.py
Normal file
66
odoo-bringout-oca-ocb-mrp_repair/mrp_repair/models/repair.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class Repair(models.Model):
|
||||
_inherit = 'repair.order'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
orders = super().create(vals_list)
|
||||
orders.action_explode()
|
||||
return orders
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
self.action_explode()
|
||||
return res
|
||||
|
||||
def action_explode(self):
|
||||
lines_to_unlink_ids = set()
|
||||
line_vals_list = []
|
||||
for op in self.operations:
|
||||
bom = self.env['mrp.bom'].sudo()._bom_find(op.product_id, company_id=op.company_id.id, bom_type='phantom')[op.product_id]
|
||||
if not bom:
|
||||
continue
|
||||
factor = op.product_uom._compute_quantity(op.product_uom_qty, bom.product_uom_id) / bom.product_qty
|
||||
_boms, lines = bom.sudo().explode(op.product_id, factor, picking_type=bom.picking_type_id)
|
||||
for bom_line, line_data in lines:
|
||||
if bom_line.product_id.type != 'service':
|
||||
line_vals_list.append(op._prepare_phantom_line_vals(bom_line, line_data['qty']))
|
||||
lines_to_unlink_ids.add(op.id)
|
||||
|
||||
self.env['repair.line'].browse(lines_to_unlink_ids).sudo().unlink()
|
||||
if line_vals_list:
|
||||
self.env['repair.line'].create(line_vals_list)
|
||||
|
||||
|
||||
class RepairLine(models.Model):
|
||||
_inherit = 'repair.line'
|
||||
|
||||
def _prepare_phantom_line_vals(self, bom_line, qty):
|
||||
self.ensure_one()
|
||||
product = bom_line.product_id
|
||||
uom = bom_line.product_uom_id
|
||||
partner = self.repair_id.partner_id
|
||||
price = self.repair_id.pricelist_id._get_product_price(product, qty, uom=uom)
|
||||
tax = self.env['account.tax']
|
||||
if partner:
|
||||
partner_invoice = self.repair_id.partner_invoice_id or partner
|
||||
fpos = self.env['account.fiscal.position']._get_fiscal_position(partner_invoice, delivery=self.repair_id.address_id)
|
||||
taxes = self.product_id.taxes_id.filtered(lambda x: x.company_id == self.repair_id.company_id)
|
||||
tax = fpos.map_tax(taxes)
|
||||
return {
|
||||
'name': self.name,
|
||||
'repair_id': self.repair_id.id,
|
||||
'type': self.type,
|
||||
'product_id': product.id,
|
||||
'price_unit': price,
|
||||
'tax_id': [(4, t.id) for t in tax],
|
||||
'product_uom_qty': qty,
|
||||
'product_uom': uom.id,
|
||||
'location_id': self.location_id.id,
|
||||
'location_dest_id': self.location_dest_id.id,
|
||||
'state': 'draft',
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_tracability
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo.addons.mrp.tests.common import TestMrpCommon
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestRepairTraceability(TestMrpCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env.ref('base.group_user').write({'implied_ids': [(4, cls.env.ref('stock.group_production_lot').id)]})
|
||||
|
||||
def test_tracking_repair_production(self):
|
||||
"""
|
||||
Test that removing a tracked component with a repair does not block the flow of using that component in another
|
||||
bom
|
||||
"""
|
||||
product_to_repair = self.env['product.product'].create({
|
||||
'name': 'product first serial to act repair',
|
||||
'tracking': 'serial',
|
||||
})
|
||||
ptrepair_lot = self.env['stock.lot'].create({
|
||||
'name': 'A1',
|
||||
'product_id': product_to_repair.id,
|
||||
'company_id': self.env.user.company_id.id
|
||||
})
|
||||
product_to_remove = self.env['product.product'].create({
|
||||
'name': 'other first serial to remove with repair',
|
||||
'tracking': 'serial',
|
||||
})
|
||||
ptremove_lot = self.env['stock.lot'].create({
|
||||
'name': 'B2',
|
||||
'product_id': product_to_remove.id,
|
||||
'company_id': self.env.user.company_id.id
|
||||
})
|
||||
# Create a manufacturing order with product (with SN A1)
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.product_id = product_to_repair
|
||||
with mo_form.move_raw_ids.new() as move:
|
||||
move.product_id = product_to_remove
|
||||
move.product_uom_qty = 1
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
# Set serial to A1
|
||||
mo.lot_producing_id = ptrepair_lot
|
||||
# Set component serial to B2
|
||||
mo.move_raw_ids.move_line_ids.lot_id = ptremove_lot
|
||||
mo.button_mark_done()
|
||||
|
||||
with Form(self.env['repair.order']) as ro_form:
|
||||
ro_form.product_id = product_to_repair
|
||||
ro_form.lot_id = ptrepair_lot # Repair product Serial A1
|
||||
with ro_form.operations.new() as operation:
|
||||
operation.type = 'remove'
|
||||
operation.product_id = product_to_remove
|
||||
operation.lot_id = ptremove_lot # Remove product Serial B2 from the product
|
||||
ro = ro_form.save()
|
||||
ro.action_validate()
|
||||
ro.action_repair_start()
|
||||
ro.action_repair_end()
|
||||
|
||||
# Create a manufacturing order with product (with SN A2)
|
||||
mo2_form = Form(self.env['mrp.production'])
|
||||
mo2_form.product_id = product_to_repair
|
||||
with mo2_form.move_raw_ids.new() as move:
|
||||
move.product_id = product_to_remove
|
||||
move.product_uom_qty = 1
|
||||
mo2 = mo2_form.save()
|
||||
mo2.action_confirm()
|
||||
# Set serial to A2
|
||||
mo2.lot_producing_id = self.env['stock.lot'].create({
|
||||
'name': 'A2',
|
||||
'product_id': product_to_repair.id,
|
||||
'company_id': self.env.user.company_id.id
|
||||
})
|
||||
# Set component serial to B2 again, it is possible
|
||||
mo2.move_raw_ids.move_line_ids.lot_id = ptremove_lot
|
||||
# We are not forbidden to use that serial number, so nothing raised here
|
||||
mo2.button_mark_done()
|
||||
|
||||
def test_mo_with_used_sn_component(self):
|
||||
"""
|
||||
Suppose a tracked-by-usn component has been used to produce a product. Then, using a repair order,
|
||||
this component is removed from the product and returned as available stock. The user should be able to
|
||||
use the component in a new MO
|
||||
"""
|
||||
def produce_one(product, component):
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.product_id = product
|
||||
with mo_form.move_raw_ids.new() as raw_line:
|
||||
raw_line.product_id = component
|
||||
raw_line.product_uom_qty = 1
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.action_assign()
|
||||
action = mo.button_mark_done()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
return mo
|
||||
|
||||
picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0]
|
||||
picking_type.use_auto_consume_components_lots = True
|
||||
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
|
||||
finished, component = self.env['product.product'].create([{
|
||||
'name': 'Finished Product',
|
||||
'type': 'product',
|
||||
}, {
|
||||
'name': 'SN Componentt',
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
}])
|
||||
|
||||
sn_lot = self.env['stock.lot'].create({
|
||||
'product_id': component.id,
|
||||
'name': 'USN01',
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)
|
||||
|
||||
mo = produce_one(finished, component)
|
||||
self.assertEqual(mo.state, 'done')
|
||||
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
||||
|
||||
ro_form = Form(self.env['repair.order'])
|
||||
ro_form.product_id = finished
|
||||
with ro_form.operations.new() as ro_line:
|
||||
ro_line.type = 'remove'
|
||||
ro_line.product_id = component
|
||||
ro_line.lot_id = sn_lot
|
||||
ro_line.location_dest_id = stock_location
|
||||
ro = ro_form.save()
|
||||
ro.action_validate()
|
||||
ro.action_repair_start()
|
||||
ro.action_repair_end()
|
||||
|
||||
mo = produce_one(finished, component)
|
||||
self.assertEqual(mo.state, 'done')
|
||||
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
||||
# Now, we will test removing the component and putting it back in stock,
|
||||
# then placing it back into the product and removing it a second time.
|
||||
# The user should be able to use the component in a new MO.
|
||||
ro_form = Form(self.env['repair.order'])
|
||||
ro_form.product_id = finished
|
||||
with ro_form.operations.new() as ro_line:
|
||||
ro_line.type = 'remove'
|
||||
ro_line.product_id = component
|
||||
ro_line.lot_id = sn_lot
|
||||
ro_line.location_dest_id = stock_location
|
||||
ro = ro_form.save()
|
||||
ro.action_validate()
|
||||
ro.action_repair_start()
|
||||
ro.action_repair_end()
|
||||
self.assertEqual(ro.state, 'done')
|
||||
# Add the component into the product
|
||||
ro_form = Form(self.env['repair.order'])
|
||||
ro_form.product_id = finished
|
||||
with ro_form.operations.new() as ro_line:
|
||||
ro_line.type = 'add'
|
||||
ro_line.product_id = component
|
||||
ro_line.lot_id = sn_lot
|
||||
ro_line.location_id = stock_location
|
||||
ro = ro_form.save()
|
||||
ro.action_validate()
|
||||
ro.action_repair_start()
|
||||
ro.action_repair_end()
|
||||
self.assertEqual(ro.state, 'done')
|
||||
# Removing it a second time
|
||||
ro_form = Form(self.env['repair.order'])
|
||||
ro_form.product_id = finished
|
||||
with ro_form.operations.new() as ro_line:
|
||||
ro_line.type = 'remove'
|
||||
ro_line.product_id = component
|
||||
ro_line.lot_id = sn_lot
|
||||
ro_line.location_dest_id = stock_location
|
||||
ro = ro_form.save()
|
||||
ro.action_validate()
|
||||
ro.action_repair_start()
|
||||
ro.action_repair_end()
|
||||
self.assertEqual(ro.state, 'done')
|
||||
# check if the removed component can be used in a new MO
|
||||
mo = produce_one(finished, component)
|
||||
self.assertEqual(mo.state, 'done')
|
||||
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
||||
|
||||
def test_mo_with_used_sn_component_02(self):
|
||||
"""
|
||||
Suppose a tracked-by-usn component has been remvoed in a repair order. Then, using to produce a product,
|
||||
but this product has been unbuild. The user should be able to use the component in a new MO
|
||||
"""
|
||||
finished, component = self.env['product.product'].create([{
|
||||
'name': 'Finished Product',
|
||||
'type': 'product',
|
||||
}, {
|
||||
'name': 'SN Componentt',
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
}])
|
||||
|
||||
sn_lot = self.env['stock.lot'].create({
|
||||
'product_id': component.id,
|
||||
'name': 'USN01',
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)
|
||||
self.assertEqual(component.qty_available, 1)
|
||||
|
||||
# create a repair order
|
||||
ro_form = Form(self.env['repair.order'])
|
||||
ro_form.product_id = self.product_1
|
||||
with ro_form.operations.new() as ro_line:
|
||||
ro_line.type = 'remove'
|
||||
ro_line.product_id = component
|
||||
ro_line.lot_id = sn_lot
|
||||
ro = ro_form.save()
|
||||
ro.action_validate()
|
||||
ro.action_repair_start()
|
||||
ro.action_repair_end()
|
||||
|
||||
# create a manufacturing order
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.product_id = finished
|
||||
with mo_form.move_raw_ids.new() as raw_line:
|
||||
raw_line.product_id = component
|
||||
raw_line.product_uom_qty = 1
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.action_assign()
|
||||
mo.move_raw_ids.move_line_ids.qty_done = 1
|
||||
action = mo.button_mark_done()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
self.assertEqual(mo.state, 'done')
|
||||
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
||||
# unbuild the mo
|
||||
unbuild_form = Form(self.env['mrp.unbuild'])
|
||||
unbuild_form.mo_id = mo
|
||||
unbuild_form.save().action_unbuild()
|
||||
# create another mo and use the same SN
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.product_id = finished
|
||||
with mo_form.move_raw_ids.new() as raw_line:
|
||||
raw_line.product_id = component
|
||||
raw_line.product_uom_qty = 1
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.action_assign()
|
||||
mo.move_raw_ids.move_line_ids.qty_done = 1
|
||||
action = mo.button_mark_done()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
self.assertEqual(mo.state, 'done')
|
||||
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
||||
|
||||
def test_mo_with_unscrapped_tracked_component(self):
|
||||
"""
|
||||
Tracked-by-sn component
|
||||
Use it in a MO
|
||||
Repair the finished product:
|
||||
Remove the component, destination: scrap location
|
||||
Move the component back to the stock
|
||||
Use it in a MO
|
||||
"""
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
scrap_location = self.env['stock.location'].search([('company_id', '=', self.env.company.id), ('scrap_location', '=', True)], limit=1)
|
||||
|
||||
finished = self.bom_4.product_id
|
||||
component = self.bom_4.bom_line_ids.product_id
|
||||
component.write({
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
})
|
||||
|
||||
sn_lot = self.env['stock.lot'].create({
|
||||
'product_id': component.id,
|
||||
'name': 'SN01',
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)
|
||||
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.bom_id = self.bom_4
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.qty_producing = 1
|
||||
mo.move_raw_ids.move_line_ids.qty_done = 1
|
||||
mo.button_mark_done()
|
||||
|
||||
ro = self.env['repair.order'].create({
|
||||
'product_id': finished.id,
|
||||
'operations': [
|
||||
(0, 0, {
|
||||
'name': 'foo',
|
||||
'product_id': component.id,
|
||||
'lot_id': sn_lot.id,
|
||||
'type': 'remove',
|
||||
'location_dest_id': scrap_location.id,
|
||||
'price_unit': 0,
|
||||
})
|
||||
],
|
||||
})
|
||||
ro.action_validate()
|
||||
ro.action_repair_start()
|
||||
ro.action_repair_end()
|
||||
|
||||
sm = self.env['stock.move'].create({
|
||||
'name': component.name,
|
||||
'product_id': component.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': component.uom_id.id,
|
||||
'location_id': scrap_location.id,
|
||||
'location_dest_id': stock_location.id,
|
||||
})
|
||||
sm._action_confirm()
|
||||
sm.move_line_ids.write({
|
||||
'qty_done': 1.0,
|
||||
'lot_id': sn_lot.id,
|
||||
})
|
||||
sm._action_done()
|
||||
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.bom_id = self.bom_4
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.qty_producing = 1
|
||||
mo.move_raw_ids.move_line_ids.qty_done = 1
|
||||
mo.button_mark_done()
|
||||
|
||||
self.assertRecordValues(mo.move_raw_ids.move_line_ids, [
|
||||
{'product_id': component.id, 'lot_id': sn_lot.id, 'qty_done': 1.0, 'state': 'done'},
|
||||
])
|
||||
43
odoo-bringout-oca-ocb-mrp_repair/pyproject.toml
Normal file
43
odoo-bringout-oca-ocb-mrp_repair/pyproject.toml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-mrp_repair"
|
||||
version = "16.0.0"
|
||||
description = "Mrp Repairs - Odoo addon"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-repair>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-mrp>=16.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/bringout/0"
|
||||
repository = "https://github.com/bringout/0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["mrp_repair"]
|
||||
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue