kalenislims/lims_account_invoice/lims.py

447 lines
16 KiB
Python
Raw Normal View History

2017-10-08 02:23:22 +02:00
# -*- coding: utf-8 -*-
# This file is part of lims_account_invoice module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
import operator
from collections import defaultdict
2017-10-08 02:23:22 +02:00
from decimal import Decimal
from trytond.model import fields
from trytond.wizard import Wizard, StateAction
from trytond.report import Report
2017-10-08 02:23:22 +02:00
from trytond.pool import Pool, PoolMeta
from trytond.pyson import PYSONEncoder
2017-10-08 02:23:22 +02:00
from trytond.transaction import Transaction
2019-07-23 23:27:33 +02:00
from trytond.exceptions import UserError
from trytond.i18n import gettext
2017-10-08 02:23:22 +02:00
2019-03-04 15:41:58 +01:00
class FractionType(metaclass=PoolMeta):
2017-10-08 02:23:22 +02:00
__name__ = 'lims.fraction.type'
invoiceable = fields.Boolean('Invoiceable')
@staticmethod
def default_invoiceable():
return True
2019-03-04 15:41:58 +01:00
class Entry(metaclass=PoolMeta):
2017-10-08 02:23:22 +02:00
__name__ = 'lims.entry'
last_release_date = fields.Function(fields.DateTime(
'Last Release date'), 'get_last_release_date')
qty_lines_pending_invoicing = fields.Function(fields.Integer(
'Lines pending invoicing'), 'get_qty_lines_pending_invoicing')
@classmethod
def get_last_release_date(cls, entries, name):
cursor = Transaction().connection.cursor()
pool = Pool()
ResultsVersion = pool.get('lims.results_report.version')
ResultsDetail = pool.get('lims.results_report.version.detail')
NotebookLine = pool.get('lims.notebook.line')
Notebook = pool.get('lims.notebook')
Fraction = pool.get('lims.fraction')
Sample = pool.get('lims.sample')
result = {}
for e in entries:
cursor.execute('SELECT MAX(rd.release_date) '
'FROM "' + ResultsVersion._table + '" rv '
'INNER JOIN "' + ResultsDetail._table + '" rd '
'ON rv.id = rd.report_version '
'INNER JOIN "' + NotebookLine._table + '" nl '
'ON nl.results_report = rv.results_report '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = nl.notebook '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = n.fraction '
'INNER JOIN "' + Sample._table + '" s '
'ON s.id = f.sample '
'WHERE s.entry = %s '
'AND rd.state = \'released\' '
'AND rd.type != \'preliminary\'',
(str(e.id),))
result[e.id] = cursor.fetchone()[0]
return result
@classmethod
def get_qty_lines_pending_invoicing(cls, entries, name):
cursor = Transaction().connection.cursor()
pool = Pool()
Sample = pool.get('lims.sample')
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
InvoiceLine = pool.get('account.invoice.line')
result = {}
for e in entries:
result[e.id] = 0
cursor.execute('SELECT srv.id '
'FROM "' + Service._table + '" srv '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = srv.fraction '
'INNER JOIN "' + Sample._table + '" s '
'ON s.id = f.sample '
'WHERE s.entry = %s',
(str(e.id),))
services_ids = [x[0] for x in cursor.fetchall()]
if not services_ids:
continue
origins = '\', \''.join(['lims.service,' + str(s)
for s in services_ids])
cursor.execute('SELECT COUNT(*) '
'FROM "' + InvoiceLine._table + '" '
'WHERE origin IN (\'' + origins + '\') '
'AND invoice IS NULL')
result[e.id] = cursor.fetchone()[0]
return result
2017-10-08 02:23:22 +02:00
@classmethod
def on_hold(cls, entries):
2020-08-06 19:52:36 +02:00
super().on_hold(entries)
2017-10-08 02:23:22 +02:00
cls.create_invoice_lines(entries)
@classmethod
def create_invoice_lines(cls, entries):
Service = Pool().get('lims.service')
2017-10-08 02:23:22 +02:00
with Transaction().set_context(_check_access=False):
services = Service.search([
2017-10-08 02:23:22 +02:00
('entry', 'in', [e.id for e in entries]),
('annulled', '=', False),
2017-10-08 02:23:22 +02:00
])
for service in services:
service.create_invoice_line()
2017-10-08 02:23:22 +02:00
@classmethod
def view_toolbar_get(cls):
if not Transaction().context.get('ready_for_invoicing', False):
return super().view_toolbar_get()
# Entries Ready for Invoicing uses specific keywords
prints = cls.get_entries_ready_for_invoicing_keyword('form_print')
actions = cls.get_entries_ready_for_invoicing_keyword('form_action')
relates = cls.get_entries_ready_for_invoicing_keyword('form_relate')
result = {
'print': prints,
'action': actions,
'relate': relates,
'exports': [],
}
return result
@classmethod
def get_entries_ready_for_invoicing_keyword(cls, keyword):
"""
Method copied from ActionKeyword. It search for specific keywords
for Entries Ready for Invoicing: lims.entry,-2
"""
pool = Pool()
ActionKeyword = pool.get('ir.action.keyword')
Action = pool.get('ir.action')
key = (keyword, ('lims.entry', -2))
keywords = ActionKeyword._get_keyword_cache.get(key)
if keywords is not None:
return keywords
clause = [
('keyword', '=', keyword),
('model', '=', 'lims.entry,-2'),
('action.active', '=', True),
]
action_keywords = ActionKeyword.search(clause, order=[])
types = defaultdict(list)
for action_keyword in action_keywords:
type_ = action_keyword.action.type
types[type_].append(action_keyword.action.id)
keywords = []
for type_, action_ids in types.items():
for value in Action.get_action_values(type_, action_ids):
value['keyword'] = keyword
keywords.append(value)
keywords.sort(key=operator.itemgetter('name'))
ActionKeyword._get_keyword_cache.set(key, keywords)
return keywords
2017-10-08 02:23:22 +02:00
2019-03-04 15:41:58 +01:00
class Fraction(metaclass=PoolMeta):
2017-10-08 02:23:22 +02:00
__name__ = 'lims.fraction'
@classmethod
def confirm(cls, fractions):
fractions_to_invoice = [f for f in fractions if
f.sample.entry.state != 'pending']
2020-08-06 19:52:36 +02:00
super().confirm(fractions)
2017-10-08 02:23:22 +02:00
if fractions_to_invoice:
cls.create_invoice_lines(fractions_to_invoice)
@classmethod
def create_invoice_lines(cls, fractions):
Service = Pool().get('lims.service')
2017-10-08 02:23:22 +02:00
with Transaction().set_context(_check_access=False):
services = Service.search([
2017-10-08 02:23:22 +02:00
('fraction', 'in', [f.id for f in fractions]),
('annulled', '=', False),
2017-10-08 02:23:22 +02:00
])
for service in services:
service.create_invoice_line()
2017-10-08 02:23:22 +02:00
2019-03-04 15:41:58 +01:00
class Service(metaclass=PoolMeta):
2017-10-08 02:23:22 +02:00
__name__ = 'lims.service'
@classmethod
def create(cls, vlist):
2020-08-06 19:52:36 +02:00
services = super().create(vlist)
2017-10-08 02:23:22 +02:00
services_to_invoice = [s for s in services if
s.entry.state == 'pending']
for service in services_to_invoice:
service.create_invoice_line()
2017-10-08 02:23:22 +02:00
return services
def create_invoice_line(self):
2017-10-08 02:23:22 +02:00
InvoiceLine = Pool().get('account.invoice.line')
if (not self.fraction.type.invoiceable or
self.fraction.cie_fraction_type):
return
invoice_line = self.get_invoice_line()
2017-10-08 02:23:22 +02:00
if not invoice_line:
return
with Transaction().set_context(_check_access=False):
InvoiceLine.create([invoice_line])
def get_invoice_line(self):
2017-10-08 02:23:22 +02:00
Company = Pool().get('company.company')
company = Transaction().context.get('company')
currency = Company(company).currency.id
product = self.analysis.product if self.analysis else None
if not product:
return
account_revenue = product.account_revenue_used
if not account_revenue:
2019-07-23 23:27:33 +02:00
raise UserError(
gettext('lims_account_invoice.msg_missing_account_revenue',
product=self.analysis.product.rec_name,
service=self.rec_name))
2017-10-08 02:23:22 +02:00
party = self.entry.invoice_party
taxes = []
pattern = {}
for tax in product.customer_taxes_used:
if party.customer_tax_rule:
tax_ids = party.customer_tax_rule.apply(tax, pattern)
if tax_ids:
taxes.extend(tax_ids)
continue
taxes.append(tax.id)
if party.customer_tax_rule:
tax_ids = party.customer_tax_rule.apply(None, pattern)
if tax_ids:
taxes.extend(tax_ids)
taxes_to_add = None
if taxes:
taxes_to_add = [('add', taxes)]
return {
'company': company,
'currency': currency,
'invoice_type': 'out',
2017-10-08 02:23:22 +02:00
'party': party,
'description': self.number + ' - ' + self.analysis.rec_name,
'origin': str(self),
'quantity': 1,
'unit': product.default_uom,
'product': product,
'unit_price': Decimal('1.00'),
'taxes': taxes_to_add,
'account': account_revenue,
}
@classmethod
def delete(cls, services):
cls.delete_invoice_lines(services)
2020-08-06 19:52:36 +02:00
super().delete(services)
2017-10-08 02:23:22 +02:00
@classmethod
def write(cls, *args):
super().write(*args)
actions = iter(args)
for services, vals in zip(actions, actions):
if vals.get('annulled'):
cls.delete_invoice_lines(services)
2017-10-08 02:23:22 +02:00
@classmethod
def delete_invoice_lines(cls, services):
InvoiceLine = Pool().get('account.invoice.line')
lines_to_delete = []
for service in services:
with Transaction().set_context(_check_access=False):
lines = InvoiceLine.search([
('origin', '=', str(service)),
])
2017-10-08 02:23:22 +02:00
lines_to_delete.extend(lines)
if lines_to_delete:
for line in lines_to_delete:
if line.invoice:
2019-07-23 23:27:33 +02:00
raise UserError(gettext(
'lims_account_invoice.msg_delete_service_invoice'))
2017-10-08 02:23:22 +02:00
with Transaction().set_context(_check_access=False,
delete_service=True):
InvoiceLine.delete(lines_to_delete)
2019-03-04 15:41:58 +01:00
class ManageServices(metaclass=PoolMeta):
__name__ = 'lims.manage_services'
def create_service(self, service, fraction):
2020-08-06 19:52:36 +02:00
new_service = super().create_service(service, fraction)
new_service.create_invoice_line()
return new_service
class EditSampleService(metaclass=PoolMeta):
__name__ = 'lims.sample.edit_service'
def create_service(self, service, fraction):
new_service = super().create_service(service, fraction)
new_service.create_invoice_line()
return new_service
class AddSampleService(metaclass=PoolMeta):
__name__ = 'lims.sample.add_service'
def create_service(self, service, fraction):
new_service = super().create_service(service, fraction)
new_service.create_invoice_line()
return new_service
class OpenEntriesReadyForInvoicing(Wizard):
'Entries Ready for Invoicing'
__name__ = 'lims.entries_ready_for_invoicing'
start_state = 'open_'
open_ = StateAction('lims_account_invoice.act_entries_ready_for_invoicing')
def _get_entries_ready_for_invoicing(self):
cursor = Transaction().connection.cursor()
pool = Pool()
InvoiceLine = pool.get('account.invoice.line')
NotebookLine = pool.get('lims.notebook.line')
Notebook = pool.get('lims.notebook')
Fraction = pool.get('lims.fraction')
Sample = pool.get('lims.sample')
cursor.execute('SELECT origin '
'FROM "' + InvoiceLine._table + '" '
'WHERE origin LIKE \'lims.service,%\' '
'AND invoice IS NULL')
invoiced_services = [x[0][13:] for x in cursor.fetchall()]
if not invoiced_services:
return []
services_ids = ', '.join(s for s in invoiced_services)
cursor.execute('SELECT DISTINCT(s.entry) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = nl.notebook '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = n.fraction '
'INNER JOIN "' + Sample._table + '" s '
'ON s.id = f.sample '
'WHERE nl.service IN (' + services_ids + ')')
invoiced_entries = [x[0] for x in cursor.fetchall()]
cursor.execute('SELECT DISTINCT(s.entry) '
'FROM "' + NotebookLine._table + '" nl '
'INNER JOIN "' + Notebook._table + '" n '
'ON n.id = nl.notebook '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = n.fraction '
'INNER JOIN "' + Sample._table + '" s '
'ON s.id = f.sample '
'WHERE nl.service IN (' + services_ids + ') '
'AND (nl.annulled = FALSE '
'AND nl.report = TRUE '
'AND nl.results_report IS NULL)')
pending_entries = [x[0] for x in cursor.fetchall()]
entries = list(set(invoiced_entries) - set(pending_entries))
if not entries:
return []
return entries
def do_open_(self, action):
entries_ids = self._get_entries_ready_for_invoicing()
action['pyson_context'] = PYSONEncoder().encode({
'ready_for_invoicing': True,
})
action['pyson_domain'] = PYSONEncoder().encode([
('id', 'in', entries_ids),
])
return action, {}
class OpenLinesPendingInvoicing(Wizard):
'Lines Pending Invoicing'
__name__ = 'lims.lines_pending_invoicing'
start_state = 'open_'
open_ = StateAction('lims_account_invoice.act_invoice_line')
def do_open_(self, action):
cursor = Transaction().connection.cursor()
pool = Pool()
Entry = pool.get('lims.entry')
Sample = pool.get('lims.sample')
Fraction = pool.get('lims.fraction')
Service = pool.get('lims.service')
InvoiceLine = pool.get('account.invoice.line')
lines_ids = []
entries = Entry.browse(Transaction().context['active_ids'])
for entry in entries:
cursor.execute('SELECT srv.id '
'FROM "' + Service._table + '" srv '
'INNER JOIN "' + Fraction._table + '" f '
'ON f.id = srv.fraction '
'INNER JOIN "' + Sample._table + '" s '
'ON s.id = f.sample '
'WHERE s.entry = %s',
(str(entry.id),))
services_ids = [x[0] for x in cursor.fetchall()]
if not services_ids:
continue
origins = '\', \''.join(['lims.service,' + str(s)
for s in services_ids])
cursor.execute('SELECT id '
'FROM "' + InvoiceLine._table + '" '
'WHERE origin IN (\'' + origins + '\') '
'AND invoice IS NULL')
lines_ids.extend(x[0] for x in cursor.fetchall())
action['pyson_domain'] = PYSONEncoder().encode([
('id', 'in', lines_ids),
])
action['name'] += ' (%s)' % ', '.join(
e.rec_name for e in entries)
return action, {}
class EntriesReadyForInvoicingSpreadsheet(Report):
'Entries Ready for Invoicing'
__name__ = 'lims.entries_ready_for_invoicing.spreadsheet'