Skip to content

Commit 1549dc7

Browse files
committed
[IMP] fieldservice_account: Simplify module
- This change removes most of the invoicing logic from fieldservice_account and puts it into its own module
1 parent fa11de4 commit 1549dc7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1847
-515
lines changed

fieldservice_account/__manifest__.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,23 @@
33

44
{
55
'name': 'Field Service - Accounting',
6-
'summary': 'Track employee time and invoice Field Service orders',
6+
'summary': 'Track invoices linked to Field Service orders',
77
'version': '12.0.2.0.1',
88
'category': 'Field Service',
99
'author': 'Open Source Integrators, Odoo Community Association (OCA)',
1010
'website': 'https://github.com/OCA/field-service',
1111
'depends': [
1212
'fieldservice',
13-
'hr_timesheet',
14-
'analytic',
1513
'account',
16-
'product',
1714
],
1815
'data': [
19-
'data/time_products.xml',
20-
'data/ir_rule.xml',
2116
'security/ir.model.access.csv',
2217
'report/fsm_order_report_template.xml',
23-
'views/account.xml',
2418
'views/fsm_location.xml',
2519
'views/fsm_order.xml',
2620
'views/fsm_person.xml',
2721
'views/fsm_route.xml',
2822
'views/account_invoice_view.xml',
29-
'views/hr_timesheet.xml'
3023
],
3124
'demo': [
3225
'demo/fsm_location.xml',
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Copyright (C) 2018 - TODAY, Open Source Integrators
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
33

4-
from odoo import api, fields, models, _
5-
from odoo.exceptions import ValidationError
4+
from odoo import fields, models
65

76

87
class AccountInvoice(models.Model):
@@ -15,40 +14,3 @@ class AccountInvoiceLine(models.Model):
1514
_inherit = "account.invoice.line"
1615

1716
fsm_order_id = fields.Many2one('fsm.order', string='FSM Order')
18-
19-
@api.model
20-
def create(self, vals):
21-
order = self.env['fsm.order'].browse(vals.get('fsm_order_id'))
22-
if order:
23-
if order.location_id.analytic_account_id:
24-
vals['account_analytic_id'] = order.location_id.\
25-
analytic_account_id.id
26-
else:
27-
raise ValidationError(_("No analytic account "
28-
"set on the order's Location."))
29-
return super(AccountInvoiceLine, self).create(vals)
30-
31-
@api.onchange('product_id', 'quantity')
32-
def onchange_product_id(self):
33-
for line in self:
34-
if line.fsm_order_id:
35-
partner = line.fsm_order_id.person_id and\
36-
line.fsm_order_id.person_id.partner_id or False
37-
if not partner:
38-
raise ValidationError(
39-
_("Please set the field service worker."))
40-
fpos = partner.property_account_position_id
41-
tmpl = line.product_id.product_tmpl_id
42-
if line.product_id:
43-
accounts = tmpl.get_product_accounts()
44-
supinfo = self.env['product.supplierinfo'].search(
45-
[('name', '=', partner.id),
46-
('product_tmpl_id', '=', tmpl.id),
47-
('min_qty', '<=', line.quantity)],
48-
order='min_qty DESC')
49-
line.price_unit = \
50-
supinfo and supinfo[0].price or tmpl.standard_price
51-
line.account_id = accounts.get('expense', False)
52-
line.invoice_line_tax_ids = fpos.\
53-
map_tax(tmpl.supplier_taxes_id)
54-
line.name = line.product_id.name

fieldservice_account/models/fsm_location.py

-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
class FSMLocation(models.Model):
88
_inherit = 'fsm.location'
99

10-
analytic_account_id = fields.Many2one('account.analytic.account',
11-
string='Analytic Account')
1210
customer_id = fields.Many2one(
1311
'res.partner', string='Billed Customer', required=True,
1412
ondelete='restrict', auto_join=True, track_visibility='onchange')

fieldservice_account/models/fsm_order.py

+2-171
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,14 @@
11
# Copyright (C) 2018 - TODAY, Open Source Integrators
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
33

4-
from odoo import _, api, fields, models
5-
from odoo.exceptions import ValidationError
6-
7-
8-
ACCOUNT_STAGES = [('draft', 'Draft'),
9-
('review', 'Needs Review'),
10-
('confirmed', 'Confirmed'),
11-
('invoiced', 'Fully Invoiced'),
12-
('no', 'Nothing Invoiced')]
4+
from odoo import api, fields, models
135

146

157
class FSMOrder(models.Model):
168
_inherit = 'fsm.order'
179

18-
contractor_cost_ids = fields.One2many('account.invoice.line',
19-
'fsm_order_id',
20-
string='Contractor Costs')
21-
employee_timesheet_ids = fields.One2many('account.analytic.line',
22-
"fsm_order_id",
23-
string='Employee Timesheets')
2410
total_cost = fields.Float(compute='_compute_total_cost',
2511
string='Total Cost')
26-
employee = fields.Boolean(compute='_compute_employee')
27-
contractor_total = fields.Float(compute='_compute_contractor_cost',
28-
string='Contractor Cost Estimate')
29-
employee_time_total = fields.Float(compute='_compute_employee_hours',
30-
string='Total Employee Hours')
31-
account_stage = fields.Selection(ACCOUNT_STAGES, string='Accounting Stage',
32-
default='draft')
3312
bill_to = fields.Selection([('location', 'Bill Location'),
3413
('contact', 'Bill Contact')],
3514
string="Bill to",
@@ -41,158 +20,10 @@ class FSMOrder(models.Model):
4120
index=True,
4221
track_visibility='always')
4322

44-
def _compute_employee(self):
45-
user = self.env['res.users'].browse(self.env.uid)
46-
for order in self:
47-
if user.employee_ids:
48-
order.employee = True
49-
50-
@api.depends('employee_timesheet_ids', 'contractor_cost_ids')
5123
def _compute_total_cost(self):
24+
""" To be overridden as needed from other modules """
5225
for order in self:
5326
order.total_cost = 0.0
54-
rate = 0
55-
for line in order.employee_timesheet_ids:
56-
rate = line.employee_id.timesheet_cost
57-
order.total_cost += line.unit_amount * rate
58-
for cost in order.contractor_cost_ids:
59-
order.total_cost += cost.price_unit * cost.quantity
60-
61-
@api.depends('employee_timesheet_ids')
62-
def _compute_employee_hours(self):
63-
for order in self:
64-
order.employee_time_total = 0.0
65-
for line in order.employee_timesheet_ids:
66-
order.employee_time_total += line.unit_amount
67-
68-
@api.depends('contractor_cost_ids')
69-
def _compute_contractor_cost(self):
70-
for order in self:
71-
order.contractor_total = 0.0
72-
for cost in order.contractor_cost_ids:
73-
order.contractor_total += cost.price_unit * cost.quantity
74-
75-
def action_complete(self):
76-
for order in self:
77-
order.account_stage = 'review'
78-
if self.person_id.supplier and not self.contractor_cost_ids:
79-
raise ValidationError(_("Cannot move to Complete " +
80-
"until 'Contractor Costs' is filled in"))
81-
if not self.person_id.supplier and not self.employee_timesheet_ids:
82-
raise ValidationError(_("Cannot move to Complete until " +
83-
"'Employee Timesheets' is filled in"))
84-
return super(FSMOrder, self).action_complete()
85-
86-
def create_bills(self):
87-
jrnl = self.env['account.journal'].search([
88-
('company_id', '=', self.env.user.company_id.id),
89-
('type', '=', 'purchase'),
90-
('active', '=', True)],
91-
limit=1)
92-
fpos = self.customer_id.property_account_position_id
93-
vals = {
94-
'partner_id': self.person_id.partner_id.id,
95-
'type': 'in_invoice',
96-
'journal_id': jrnl.id or False,
97-
'fiscal_position_id': fpos.id or False,
98-
'fsm_order_id': self.id
99-
}
100-
bill = self.env['account.invoice'].sudo().create(vals)
101-
for line in self.contractor_cost_ids:
102-
line.invoice_id = bill
103-
bill.compute_taxes()
104-
105-
def account_confirm(self):
106-
for order in self:
107-
contractor = order.person_id.partner_id.supplier
108-
if order.contractor_cost_ids:
109-
if contractor:
110-
order.create_bills()
111-
order.account_stage = 'confirmed'
112-
else:
113-
raise ValidationError(_("The worker assigned to this order"
114-
" is not a supplier"))
115-
if order.employee_timesheet_ids:
116-
order.account_stage = 'confirmed'
117-
118-
def account_create_invoice(self):
119-
jrnl = self.env['account.journal'].search([
120-
('company_id', '=', self.env.user.company_id.id),
121-
('type', '=', 'sale'),
122-
('active', '=', True)],
123-
limit=1)
124-
if self.bill_to == 'contact':
125-
if not self.customer_id:
126-
raise ValidationError(_("Contact empty"))
127-
fpos = self.customer_id.property_account_position_id
128-
vals = {
129-
'partner_id': self.customer_id.id,
130-
'type': 'out_invoice',
131-
'journal_id': jrnl.id or False,
132-
'fiscal_position_id': fpos.id or False,
133-
'fsm_order_id': self.id
134-
}
135-
invoice = self.env['account.invoice'].sudo().create(vals)
136-
price_list = invoice.partner_id.property_product_pricelist
137-
else:
138-
fpos = self.location_id.customer_id.property_account_position_id
139-
vals = {
140-
'partner_id': self.location_id.customer_id.id,
141-
'type': 'out_invoice',
142-
'journal_id': jrnl.id or False,
143-
'fiscal_position_id': fpos.id or False,
144-
'fsm_order_id': self.id
145-
}
146-
invoice = self.env['account.invoice'].sudo().create(vals)
147-
price_list = invoice.partner_id.property_product_pricelist
148-
for line in self.employee_timesheet_ids:
149-
price = price_list.get_product_price(product=line.product_id,
150-
quantity=line.unit_amount,
151-
partner=invoice.partner_id,
152-
date=False,
153-
uom_id=False)
154-
template = line.product_id.product_tmpl_id
155-
accounts = template.get_product_accounts()
156-
account = accounts['income']
157-
vals = {
158-
'product_id': line.product_id.id,
159-
'account_analytic_id': line.account_id.id,
160-
'quantity': line.unit_amount,
161-
'name': line.name,
162-
'price_unit': price,
163-
'account_id': account.id,
164-
'invoice_id': invoice.id
165-
}
166-
time_cost = self.env['account.invoice.line'].create(vals)
167-
taxes = template.taxes_id
168-
time_cost.invoice_line_tax_ids = fpos.map_tax(taxes)
169-
for cost in self.contractor_cost_ids:
170-
price = price_list.get_product_price(product=cost.product_id,
171-
quantity=cost.quantity,
172-
partner=invoice.partner_id,
173-
date=False,
174-
uom_id=False)
175-
template = cost.product_id.product_tmpl_id
176-
accounts = template.get_product_accounts()
177-
account = accounts['income']
178-
vals = {
179-
'product_id': cost.product_id.id,
180-
'account_analytic_id': cost.account_analytic_id.id,
181-
'quantity': cost.quantity,
182-
'name': cost.name,
183-
'price_unit': price,
184-
'account_id': account.id,
185-
'invoice_id': invoice.id
186-
}
187-
con_cost = self.env['account.invoice.line'].create(vals)
188-
taxes = template.taxes_id
189-
con_cost.invoice_line_tax_ids = fpos.map_tax(taxes)
190-
invoice.compute_taxes()
191-
self.account_stage = 'invoiced'
192-
return invoice
193-
194-
def account_no_invoice(self):
195-
self.account_stage = 'no'
19627

19728
@api.onchange('location_id')
19829
def _onchange_location_id_customer_account(self):
+1-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
To configure this module, you need to:
2-
3-
* Go to Field Service > Master Data > Locations
4-
* Create or select a location and set their analytic account
1+
No special configuration instructions.
+3-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
This module adds the ability to track employee time and contractor
2-
costs for Field Service Orders. It also adds functionality to create
3-
a customer invoice and a vendor bill when a Field Service Order is
4-
completed.
1+
This module adds the ability to link field service orders to invoices.
2+
It also adds the option to track the billing partner for field service
3+
locations and orders.

fieldservice_account/readme/USAGE.rst

+1-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1 @@
1-
To use the module:
2-
3-
On a field service order, open the "Accounting" tab. Depending on
4-
whether the logged in user or an employee or not, they will see
5-
either a place to enter timesheet records or contractor costs.
6-
7-
The total cost of the order is calculated based on the entries in
8-
the employee timesheet entries and contractor costs.
9-
10-
When an order is completed, a customer invoice will be generated for
11-
the employee time and the contractor costs. A vendor bill will be
12-
created for the contractor costs.
1+
No specific usage instructions.
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,2 @@
11
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
22
access_account_invoice_line_fsm_user,account.invoice.line.fsm.user,model_account_invoice_line,fieldservice.group_fsm_user,1,1,1,1
3-
access_account_analytic_line_fsm_manager,account.analytic.line.manager,model_account_analytic_line,fieldservice.group_fsm_manager,1,1,1,1
4-
access_account_analytic_line_fsm_dispatcher,account.analytic.line.dispatcher,model_account_analytic_line,fieldservice.group_fsm_dispatcher,1,1,1,0
5-
access_analytic_account_fsm_manager,account.analytic.account.manager,analytic.model_account_analytic_account,fieldservice.group_fsm_manager,1,1,1,1
6-
access_analytic_account_fsm_dispatcher,account.analytic.account.dispatcher,analytic.model_account_analytic_account,fieldservice.group_fsm_dispatcher,1,1,1,0

0 commit comments

Comments
 (0)