Skip to content

Commit

Permalink
[ADD] field service stock 0.0.2 (OCA#49)
Browse files Browse the repository at this point in the history
* [ADD] field service stock 0.0.2

[UPD] README.rst
  • Loading branch information
brian10048 authored and Freni-OSI committed Jul 5, 2021
1 parent 6578269 commit 1b2d4b5
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 31 deletions.
9 changes: 7 additions & 2 deletions fieldservice_stock/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,19 @@ Configuration

To configure this module, you need to:

* Go to Field Service > Configuration > Settings
* Set Inventory Locations for FSM Locations and FSM Vehicles
* Verify procurement routes

Usage
=====

To use this module, you need to:

* Set Inventory Locations for FSM Locations and FSM Vehicles
* Create a new field service order
* Under the Materials tab, add products with quantity
* Confirm an order to create stock moves
* Validate stock moves in the Inventory app
* Quantities Delivered on FSM Order Line will be updated based on move

Known issues / Roadmap
======================
Expand Down
6 changes: 3 additions & 3 deletions fieldservice_stock/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
{
'name': 'Field Service Stock add-on',
'summary': 'Inventory and Stock operations for Field Service',
'version': '11.0.0.0.1',
'version': '11.0.0.0.2',
'category': 'Field Service',
'author': "Open Source Integrators, "
"Brian McMaster, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/field-service',
'depends': [
'base_geolocalize',
'mail',
'fieldservice',
'stock',
],
'data': [
'data/fsm_stock_data.xml',
'views/fsm_location.xml',
'views/fsm_vehicle.xml',
'views/fsm_order.xml',
'views/inventory.xml',
],
'installable': True,
'license': 'AGPL-3',
Expand Down
46 changes: 25 additions & 21 deletions fieldservice_stock/data/fsm_stock_data.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>

<record id="stock_location_field" model="stock.location">
<field name="name">Field</field>
<field name="usage">view</field>
Expand All @@ -12,9 +13,18 @@
<field name="usage">view</field>
<field name="company_id"></field>
</record>

<record id="stock_location_vehicle_storage" model="stock.location">
<field name="name">Storage</field>
<field name="usage">internal</field>
<field name="location_id" ref="stock_location_vehicle"/>
<field name="company_id"></field>
</record>

</data>

<data noupdate="1">

<!-- Output to Vehicle operations -->
<record id="seq_picking_type_output_to_vehicle" model="ir.sequence">
<field name="name">Vehicle Loading</field>
Expand All @@ -32,22 +42,6 @@
<field name="default_location_dest_id" ref="stock_location_vehicle"/>
</record>

<record id="route_output_to_vehicle" model='stock.location.route'>
<field name="name">Output to Vehicle</field>
<field name="sequence">3</field>
<field name="company_id"></field>
</record>

<record id="procurement_rule_output_to_vehicle" model="procurement.rule">
<field name="name">Warehouse → Vehicle</field>
<field name="action">move</field>
<field name="location_id" ref="stock_location_vehicle"/>
<field name="location_src_id" ref="stock.stock_location_stock"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="route_output_to_vehicle"/>
<field name="picking_type_id" ref="picking_type_output_to_vehicle"/>
</record>

<!-- Vehicle to Location operations -->
<record id="seq_picking_type_vehicle_to_location" model="ir.sequence">
<field name="name">Location Delivery</field>
Expand All @@ -64,8 +58,8 @@
<field name="default_location_src_id" ref="stock_location_vehicle"/>
</record>

<record id="route_vehicle_to_location" model='stock.location.route'>
<field name="name">Vehicle to Location</field>
<record id="route_stock_to_vehicle_to_location" model='stock.location.route'>
<field name="name">Stock to Vehicle to Location</field>
<field name="sequence">3</field>
<field name="company_id"></field>
</record>
Expand All @@ -74,12 +68,22 @@
<field name="name">Vehicle → Location</field>
<field name="action">move</field>
<field name="location_id" ref="stock.stock_location_customers"/>
<field name="location_src_id" ref="stock_location_vehicle"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="route_vehicle_to_location"/>
<field name="location_src_id" ref="stock_location_vehicle_storage"/>
<field name="procure_method">make_to_order</field>
<field name="route_id" ref="route_stock_to_vehicle_to_location"/>
<field name="picking_type_id" ref="picking_type_vehicle_to_location"/>
</record>

<record id="procurement_rule_output_to_vehicle" model="procurement.rule">
<field name="name">Warehouse → Vehicle</field>
<field name="action">move</field>
<field name="location_id" ref="stock_location_vehicle_storage"/>
<field name="location_src_id" ref="stock.stock_location_stock"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="route_stock_to_vehicle_to_location"/>
<field name="picking_type_id" ref="picking_type_output_to_vehicle"/>
</record>

<!-- Location Return Operations -->
<record id="route_location_return" model='stock.location.route'>
<field name="name">Location Return</field>
Expand Down
4 changes: 3 additions & 1 deletion fieldservice_stock/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@

from . import (
fsm_location,
fsm_vehicle
fsm_vehicle,
fsm_order,
stock,
)
204 changes: 204 additions & 0 deletions fieldservice_stock/models/fsm_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# Copyright (C) 2018 - TODAY, Brian McMaster
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from datetime import datetime, timedelta

from odoo import api, fields, models

from odoo.tools import (DEFAULT_SERVER_DATETIME_FORMAT,
float_is_zero, float_compare)
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError

from odoo.addons.base_geoengine import geo_model


class FSMOrder(geo_model.GeoModel):
_inherit = 'fsm.order'

line_ids = fields.One2many(
'fsm.order.line', 'order_id', string="Order Lines",)
picking_ids = fields.One2many('stock.picking', 'fsm_order_id',
string='Transfers')
procurement_group_id = fields.Many2one(
'procurement.group', 'Procurement Group', copy=False)

def action_confirm(self):
self.line_ids._confirm_picking()
return super(FSMOrder, self).action_confirm()


class FSMOrderLine(models.Model):
_name = 'fsm.order.line'
_description = "FSM Order Lines"
_order = 'order_id, sequence, id'

order_id = fields.Many2one(
'fsm.order', string="FSM Order", required=True,
ondelete='cascade', index=True, copy=False,
readonly=True, states={'draft': [('readonly', False)]})
name = fields.Char(string='Description', required=True,
readonly=True, states={'draft': [('readonly', False)]})
sequence = fields.Integer(string='Sequence', default=10, readonly=True,
states={'draft': [('readonly', False)]})
product_id = fields.Many2one(
'product.product', string="Product", required=True,
domain=[('type', '=', 'product')], ondelete='restrict',
readonly=True, states={'draft': [('readonly', False)]})
product_uom_id = fields.Many2one(
'product.uom', string='Unit of Measure', required=True,
readonly=True, states={'draft': [('readonly', False)]})
qty_ordered = fields.Float(
string='Quantity Requested', readonly=True,
states={'draft': [('readonly', False)]},
digits=dp.get_precision('Product Unit of Measure'))
qty_delivered = fields.Float(
string='Quantity Delivered', readonly=True, copy=False,
digits=dp.get_precision('Product Unit of Measure'))
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('partial', 'Partially Shipped'),
('done', 'Done')],
string='State', compute='_compute_state', copy=False, index=True,
readonly=True, store=True)
move_ids = fields.One2many(
'stock.move', 'fsm_order_line_id', string='Stock Moves',
readonly=True, states={'draft': [('readonly', False)]})

@api.depends('move_ids', 'qty_ordered', 'qty_delivered')
def _compute_state(self):
precision = self.env['decimal.precision'].precision_get(
'Product Unit of Measure')
for line in self:
if line.move_ids and not float_is_zero(line.qty_delivered,
precision_digits=precision):
if float_compare(line.qty_delivered, line.qty_ordered,
precision_digits=precision) == -1:
line.state = 'partial'
elif float_compare(line.qty_delivered, line.qty_ordered,
precision_digits=precision) >= 0:
line.state = 'done'
elif line.move_ids:
line.state = 'confirmed'
else:
line.state = 'draft'

@api.multi
@api.onchange('product_id')
def onchange_product_id(self):
if not self.product_id:
return {'domain': {'product_uom': []}}

vals = {}
domain = {'product_uom': [('category_id', '=',
self.product_id.uom_id.category_id.id)]}

if (not self.product_uom_id
or (self.product_id.uom_id.id != self.product_uom_id.id)):
vals['product_uom_id'] = self.product_id.uom_id
vals['qty_ordered'] = 1.0

product = self.product_id.with_context(
quantity=vals.get('qty_ordered') or self.qty_ordered,
uom=self.product_uom_id.id,
)

result = {'domain': domain}

name = product.name_get()[0][1]
if product.description_sale:
name += '\n' + product.description_sale
vals['name'] = name

self.update(vals)
return result

@api.multi
def _prepare_procurement_values(self, group_id=False):
self.ensure_one()
values = {}
date_planned = (self.order_id.scheduled_date_start
or self.order_id.requested_date
or (datetime.now() + timedelta(days=1)).strftime(
DEFAULT_SERVER_DATETIME_FORMAT))
values.update({
'group_id': group_id,
'fsm_order_line_id': self.id,
'date_planned': date_planned,
'route_ids': self.env.ref(
'fieldservice_stock.route_stock_to_vehicle_to_location'),
'partner_dest_id': self.order_id.customer_id
})
return values

def _get_procurement_qty(self):
self.ensure_one()
qty = 0.0
for move in self.move_ids.filtered(lambda r: r.state != 'cancel'):
if move.picking_code == 'outgoing':
qty += move.product_uom._compute_quantity(
move.product_uom_qty, self.product_uom_id,
rounding_method='HALF-UP')
elif move.picking_code == 'incoming':
qty -= move.product_uom._compute_quantity(
move.product_uom_qty, self.product_uom_id,
rounding_method='HALF-UP')
return qty

@api.multi
def _confirm_picking(self):
precision = self.env['decimal.precision'].precision_get(
'Product Unit of Measure')
errors = []
for line in self:
qty_procured = line._get_procurement_qty()
if float_compare(qty_procured, line.qty_ordered,
precision_digits=precision) >= 0:
continue
group_id = line.order_id.procurement_group_id
if not group_id:
group_id = self.env['procurement.group'].create({
'name': line.order_id.name,
'move_type': 'direct',
'fsm_order_id': line.order_id.id,
'partner_id': line.order_id.customer_id.id,
})
line.order_id.procurement_group_id = group_id
values = line._prepare_procurement_values(group_id=group_id)
qty_needed = line.qty_ordered - qty_procured
procurement_uom = line.product_uom_id
quant_uom = line.product_id.uom_id
get_param = self.env['ir.config_parameter'].sudo().get_param
if (procurement_uom.id != quant_uom.id
and get_param('stock.propagate_uom') != '1'):
qty_needed = line.product_uom_id._compute_quantity(
qty_needed, quant_uom, rounding_method='HALF-UP')
procurement_uom = quant_uom
try:
self.env['procurement.group'].run(
line.product_id, qty_needed, procurement_uom,
line.order_id.fsm_location_id.inventory_location,
line.name, line.order_id.name, values)
except UserError as error:
errors.append(error.name)
if errors:
raise UserError('\n'.join(errors))
return True

@api.multi
def _get_delivered_qty(self):
self.ensure_one()
qty = 0.0
for move in self.move_ids.filtered(lambda r: r.state == 'done'
and not r.scrapped):
if move.location_dest_id.usage == "customer":
if (not move.origin_returned_move_id
or (move.origin_returned_move_id and move.to_refund)):
qty += move.product_uom._compute_quantity(
move.product_uom_qty, self.product_uom_id)
elif (move.location_dest_id.usage != "customer"
and move.to_refund):
qty -= move.product_uom._compute_quantity(
move.product_uom_qty, self.product_uom_id)
return qty
Loading

0 comments on commit 1b2d4b5

Please sign in to comment.