1093 lines
40 KiB
Python
Executable File
1093 lines
40 KiB
Python
Executable File
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
from datetime import date
|
|
from decimal import Decimal
|
|
|
|
from trytond.model import Workflow, ModelView, ModelSQL, DeactivableMixin, fields
|
|
from trytond.pyson import Eval, If, In, Get, Bool, Id
|
|
from trytond.transaction import Transaction
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.wizard import Wizard, StateReport, StateTransition, StateView, Button
|
|
from trytond.report import Report
|
|
from trytond.exceptions import UserError
|
|
from trytond.modules.company.model import employee_field
|
|
from trytond.modules.product import round_price
|
|
# from trytond.i18n import gettext
|
|
# from .exceptions import ValidatePaymentTermParty
|
|
|
|
STATES = {
|
|
'readonly': Eval('state') != 'draft',
|
|
}
|
|
|
|
STATES_OPEN = {
|
|
'readonly': Eval('state') != 'reception',
|
|
'required': Eval('state') == 'finished',
|
|
}
|
|
|
|
STATES_CLOSE = {
|
|
'readonly': Eval('state') != 'reception',
|
|
'required': Eval('state') == 'finished',
|
|
}
|
|
|
|
LINE_STATES = {
|
|
'readonly': ~Eval('order_state').in_(['draft', None])
|
|
}
|
|
|
|
|
|
class Order(Workflow, ModelSQL, ModelView):
|
|
'Service Order'
|
|
__name__ = 'laboratory.order'
|
|
_rec_name = 'number'
|
|
number = fields.Char('Number', readonly=True)
|
|
reference = fields.Char('Reference', states=STATES)
|
|
customer = fields.Many2One('party.party', 'Customer', required=True,
|
|
states=STATES, select=True)
|
|
patient = fields.Many2One('party.party', 'Patient', states=STATES)
|
|
doctor = fields.Many2One('party.party', 'Doctor', domain=[
|
|
('categories', '=', Eval('doctor_category'))
|
|
], depends=['doctor_category'])
|
|
request_type = fields.Selection([
|
|
('assistential', 'Assistential'),
|
|
('extramural', 'Extra-mural'),
|
|
('', ''),
|
|
], 'Request Type')
|
|
description = fields.Char('Description', states=STATES)
|
|
order_date = fields.Date('Order Date', required=True, states=STATES)
|
|
copago = fields.Numeric('Copago')
|
|
cuota_moderadora = fields.Numeric('Cuota Moderadora')
|
|
delivery = fields.Numeric('Delivery Amount', states=STATES)
|
|
discount_amount = fields.Numeric('Discount Amount', states=STATES)
|
|
discount_rate = fields.Function(fields.Numeric(
|
|
"Discount Rate", digits=(16, 2),
|
|
states={
|
|
'readonly': Eval('state') != 'draft',
|
|
},
|
|
depends=['state']),
|
|
'on_change_with_discount_rate', setter='set_discount_rate')
|
|
received_by = employee_field("Received By")
|
|
company = fields.Many2One(
|
|
'company.company', 'Company', required=True,
|
|
states=STATES,
|
|
domain=[('id', If(
|
|
In('company', Eval('context', {})), '=', '!='),
|
|
Get(Eval('context', {}), 'company', 0))
|
|
])
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('processed', 'Processed'),
|
|
('invoiced', 'Invoiced'),
|
|
('paid', 'Paid'),
|
|
('cancelled', 'Cancelled'),
|
|
], 'State', readonly=True, required=True)
|
|
invoice_type = fields.Selection([
|
|
('P', 'POS'),
|
|
('M', 'Manual'),
|
|
('91', 'Nota Crédito Eléctronica'),
|
|
('1', 'Venta Electronica'),
|
|
], 'Invoice Type')
|
|
method_process = fields.Selection([
|
|
('courtesy', 'Courtesy'),
|
|
('intern', 'Intern'),
|
|
('invoice', 'Invoice'),
|
|
('delivery', 'Delivery'),
|
|
], 'Method Process')
|
|
state_string = state.translated('state')
|
|
status_invoice = fields.Selection([
|
|
('without_invoice', 'Without Invoice'),
|
|
('partial_invoice', 'Partial Invoice'),
|
|
('invoiced', 'Invoiced'),
|
|
], 'Status Invoice', readonly=True, required=True)
|
|
state_string = state.translated('state')
|
|
price_list = fields.Many2One('product.price_list', 'Price List',
|
|
states=STATES)
|
|
lines = fields.One2Many('laboratory.order.line', 'order',
|
|
'Lines', states=STATES)
|
|
comment = fields.Text('Comment', states=STATES)
|
|
payment_term = fields.Many2One('account.invoice.payment_term',
|
|
'Payment Term', states=STATES, depends=['state'])
|
|
copago_invoice = fields.Many2One('account.invoice',
|
|
'Copago Invoice', states=STATES, depends=['state'])
|
|
copago_invoice_state = fields.Function(fields.Char(
|
|
'Copago Invoice State'), 'get_invoice_state')
|
|
invoice = fields.Many2One('account.invoice', 'Invoice', states=STATES,
|
|
depends=['state'])
|
|
invoice_state = fields.Function(fields.Char(
|
|
'Invoice State'), 'get_invoice_state')
|
|
doctor_category = fields.Function(fields.Many2One('party.category', 'Doctor Category'),
|
|
'get_doctor_category')
|
|
operation_center = fields.Many2One('company.operation_center', 'Operation Center',
|
|
states=STATES, depends=['state'])
|
|
payments = fields.One2Many('laboratory.order.payment', 'order',
|
|
'Payments', states=STATES)
|
|
total_amount = fields.Function(fields.Numeric('Total Amount',
|
|
digits=(16, 2)), 'get_total_amount')
|
|
total_neto = fields.Function(fields.Numeric('Total Neto',
|
|
digits=(16, 2)), 'get_total_neto')
|
|
unidentified_party = fields.Char('Unidentified Party')
|
|
unidentified_party_type = fields.Selection([
|
|
('AS', 'Unidentified Adult'),
|
|
('MS', 'Unidentified Child'),
|
|
('', ''),
|
|
], 'Unidentified Party Type')
|
|
sex = fields.Selection([
|
|
('male', 'M'),
|
|
('female', 'F'),
|
|
('', ''),
|
|
], 'Sex')
|
|
birthday = fields.Date('Birthday')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Order, cls).__setup__()
|
|
cls._order.insert(0, ('create_date', 'DESC'))
|
|
cls._order.insert(1, ('id', 'DESC'))
|
|
cls._transitions |= set((
|
|
('draft', 'processed'),
|
|
('processed', 'invoiced'),
|
|
('processed', 'paid'),
|
|
('invoiced', 'paid'),
|
|
))
|
|
cls._buttons.update({
|
|
'draft': {
|
|
'invisible': Eval('state').in_(['draft', 'processed'])
|
|
},
|
|
'cancel': {
|
|
'invisible': Eval('state').in_(['cancelled', 'draft', 'processed']),
|
|
},
|
|
'process': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
'process_einvoice': {
|
|
'invisible': Eval('state') != 'processed',
|
|
},
|
|
'reconcile': {
|
|
'invisible': ~Eval('state').in_(['processed','invoiced'])
|
|
},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company') or False
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_method_process():
|
|
return 'invoice'
|
|
|
|
@staticmethod
|
|
def default_invoice_type():
|
|
return '1'
|
|
|
|
@staticmethod
|
|
def default_status_invoice():
|
|
return 'without_invoice'
|
|
|
|
@staticmethod
|
|
def default_order_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
@staticmethod
|
|
def default_operation_center():
|
|
Op_center = Pool().get('company.operation_center')
|
|
op_centers = Op_center.search([])
|
|
if len(op_centers) == 1:
|
|
return op_centers[0].id
|
|
|
|
@classmethod
|
|
def copy(cls, orders, default=None):
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default.setdefault('payments', None)
|
|
return super().copy(orders, default=default)
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
return super(Order, cls).create(vlist)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('processed')
|
|
def process(cls, records):
|
|
for rec in records:
|
|
rec.set_number()
|
|
transaction = Transaction()
|
|
if rec.method_process in ('invoice', 'delivery'):
|
|
with transaction.set_context(_skip_warnings=True):
|
|
cls.execute_processing(rec)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
# @Workflow.transition('invoiced')
|
|
def process_einvoice(cls, records):
|
|
for rec in records:
|
|
transaction = Transaction()
|
|
with transaction.set_context(_skip_warnings=True):
|
|
cls.execute_processing_invoice(rec)
|
|
if rec.customer.sale_invoice_grouping_method != 'standard':
|
|
rec.state = 'invoiced'
|
|
rec.save()
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancelled')
|
|
def cancel(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
def draft(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def reconcile(cls, records):
|
|
for record in records:
|
|
cls.reconcile_order(record)
|
|
|
|
@classmethod
|
|
def delete(cls, records):
|
|
for record in records:
|
|
if record.number:
|
|
raise UserError('delete_numbered')
|
|
super(Order, cls).delete(records)
|
|
|
|
@classmethod
|
|
def execute_processing(cls, args):
|
|
print('......execute_....')
|
|
record = args
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
configuration = pool.get('laboratory.configuration')(1)
|
|
invoices = Invoice.search(['reference', '=', record.number])
|
|
if invoices and (not record.invoice or not record.copago_invoice):
|
|
if record.copago:
|
|
record.copago_invoice = invoices[0]
|
|
else:
|
|
record.invoice = invoices[0]
|
|
record.save()
|
|
|
|
if not record.invoice and not record.copago_invoice:
|
|
invoice = record.create_invoice()
|
|
Invoice = Pool().get('account.invoice')
|
|
if invoice and invoice.state == 'draft' and configuration.validate_invoice:
|
|
Invoice.validate_invoice([invoice])
|
|
|
|
@classmethod
|
|
def execute_processing_invoice(cls, record):
|
|
transaction = Transaction()
|
|
with transaction.set_context(_skip_warnings=True):
|
|
invoice = record.invoice or record.copago_invoice
|
|
if invoice:
|
|
record.process_invoice(invoice)
|
|
if record.payments:
|
|
record.process_payment()
|
|
if invoice and record.payments:
|
|
cls.reconcile_order(record)
|
|
|
|
def get_invoice_state(self, name=None):
|
|
if name == 'copago_invoice_state' and self.copago_invoice:
|
|
return self.copago_invoice.state_string
|
|
if name == 'invoice_state' and self.invoice:
|
|
return self.invoice.state_string
|
|
|
|
def get_doctor_category(self, name=None):
|
|
Config = Pool().get('laboratory.configuration')
|
|
config = Config.get_configuration()
|
|
return config.doctor_category.id if config.doctor_category else None
|
|
|
|
def get_total_amount(self, name=None):
|
|
res = []
|
|
for line in self.lines:
|
|
res.append(line.unit_price * Decimal(line.quantity))
|
|
return sum(res)
|
|
|
|
def get_total_neto(self, name=None):
|
|
if self.total_amount:
|
|
discount_amount = self.discount_amount if self.discount_amount else 0
|
|
return self.total_amount - discount_amount
|
|
|
|
@classmethod
|
|
def reconcile_order(cls, order):
|
|
pool = Pool()
|
|
invoice = order.invoice or order.copago_invoice
|
|
# to_reconcile
|
|
if invoice.state in ('draft', 'validated'):
|
|
transaction = Transaction()
|
|
with transaction.set_context(_skip_warnings=True):
|
|
order.process_invoice(invoice)
|
|
elif invoice.state == 'paid':
|
|
cls.write([order], {'state': 'paid', 'status_invoice': 'invoiced'})
|
|
|
|
payments_invoice = [l.id for l in invoice.payment_lines]
|
|
to_reconcile = []
|
|
if invoice.move:
|
|
to_reconcile = [
|
|
l.id for l in invoice.move.lines if l.account == invoice.account
|
|
]
|
|
if order.payments and invoice and invoice.move:
|
|
Invoice = pool.get('account.invoice')
|
|
Payment = pool.get('laboratory.order.payment')
|
|
Reconciliation = pool.get('account.move.reconciliation')
|
|
for payment in order.payments:
|
|
if not payment.move or (invoice.total_amount < payment.amount):
|
|
continue
|
|
_lines = [
|
|
l.id for l in payment.move.lines if l.account == invoice.account and l.id not in payments_invoice
|
|
]
|
|
Invoice.write([invoice], {'payment_lines': [('add', _lines)]})
|
|
to_reconcile.extend(_lines)
|
|
|
|
if order.check_to_reconcile(to_reconcile):
|
|
Reconciliation.create([{
|
|
'lines': [('add', to_reconcile)],
|
|
'date': date.today(),
|
|
}])
|
|
cls.write([order], {'state': 'paid', 'status_invoice': 'invoiced'})
|
|
Payment.write(list(order.payments), {'reconciled': True})
|
|
|
|
if invoice.state == 'posted' and order.discount_amount and len(invoice.charges) < 1\
|
|
and order.company.currency.is_zero(invoice.amount_to_pay - order.discount_amount):
|
|
order.create_move_to_reconcile(invoice, list(set(to_reconcile + payments_invoice)))
|
|
|
|
@fields.depends('discount_amount')
|
|
def on_change_with_discount_rate(self, name=None):
|
|
if self.discount_amount is None:
|
|
return
|
|
rate = (self.discount_amount / self.total_amount) * 100
|
|
return rate
|
|
|
|
@fields.depends('discount_rate', 'discount_amount', 'total_amount')
|
|
def on_change_discount_rate(self):
|
|
print(self.discount_rate/100, (self.total_amount * (self.discount_rate/100)))
|
|
self.discount_amount = self.total_amount * (self.discount_rate/100)
|
|
|
|
@classmethod
|
|
def set_discount_rate(cls, lines, name, value):
|
|
pass
|
|
|
|
@fields.depends('price_list', 'customer', 'payment_term')
|
|
def on_change_customer(self):
|
|
if self.customer:
|
|
self.price_list = self.customer.sale_price_list
|
|
self.payment_term = self.customer.customer_payment_term
|
|
|
|
@fields.depends('patient', 'unidentified_party')
|
|
def on_change_patient(self):
|
|
self.unidentified_party = None
|
|
|
|
def create_move_to_reconcile(self, invoice, lines_to_reconcile):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
MoveLine = pool.get('account.move.line')
|
|
Period = pool.get('account.period')
|
|
Journal = pool.get('account.journal')
|
|
Configuration = pool.get('laboratory.configuration')
|
|
Reconciliation = pool.get('account.move.reconciliation')
|
|
config = Configuration(1)
|
|
account = config.account_discount
|
|
|
|
_date = invoice.invoice_date
|
|
period_id = Period.find(self.company.id, date=_date)
|
|
journal, = Journal.search([('type', '=', 'general'), ('code', '=', 'GEN')])
|
|
if (self.copago or self.copago == 0) or (self.cuota_moderadora or self.cuota_moderadora == 0):
|
|
party = self.patient
|
|
else:
|
|
party = self.customer
|
|
_move = {
|
|
'journal': journal.id,
|
|
'period': period_id,
|
|
'date': str(_date),
|
|
'state': 'draft',
|
|
'description': '',
|
|
}
|
|
move, = Move.create([_move])
|
|
debit = round(self.discount_amount, 2)
|
|
lines = [{
|
|
'description': self.number,
|
|
'account': account.id,
|
|
'party': party.id,
|
|
'debit': debit,
|
|
'credit': Decimal('0.0'),
|
|
'move': move.id,
|
|
'reference': self.reference,
|
|
'operation_center': self.operation_center.id,
|
|
}]
|
|
credit = debit
|
|
|
|
line_add = {
|
|
'description': self.number,
|
|
'party': party.id,
|
|
'account': invoice.account.id,
|
|
'debit': Decimal('0.0'),
|
|
'credit': credit,
|
|
'move': move.id,
|
|
'reference': self.reference,
|
|
}
|
|
lines.append(line_add)
|
|
MoveLine.create(lines)
|
|
Move.post([move])
|
|
lines_to_reconcile += [l.id for l in move.lines if l.account.id == invoice.account.id]
|
|
if self.check_to_reconcile(lines_to_reconcile):
|
|
Reconciliation.create([{
|
|
'lines': [('add', lines_to_reconcile)],
|
|
'date': date.today(),
|
|
}])
|
|
self.write([self], {'state': 'paid', 'status_invoice': 'invoiced'})
|
|
|
|
def check_to_reconcile(self, lines):
|
|
MoveLine = Pool().get('account.move.line')
|
|
lines = MoveLine.browse(lines)
|
|
amount = sum([(line.debit - line.credit) for line in lines])
|
|
if self.company.currency.is_zero(amount):
|
|
return True
|
|
return False
|
|
|
|
def _create_move_payment(self, payment):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
MoveLine = pool.get('account.move.line')
|
|
Period = pool.get('account.period')
|
|
_date = payment.payment_date
|
|
period_id = Period.find(self.company.id, date=_date)
|
|
payment_mode = payment.payment_mode
|
|
str_origin = 'laboratory.order.payment,' + str(payment.id)
|
|
Configuration = pool.get('laboratory.configuration')
|
|
config = Configuration(1)
|
|
_move = {
|
|
'journal': payment_mode.journal.id,
|
|
'period': period_id,
|
|
'date': str(_date),
|
|
'state': 'draft',
|
|
'origin': str_origin,
|
|
'description': self.description,
|
|
}
|
|
move, = Move.create([_move])
|
|
order = payment.order
|
|
debit = round(payment.amount, 2)
|
|
lines = [{
|
|
'description': order.number,
|
|
'account': payment_mode.account.id,
|
|
'party': None if config.bill_copago else order.patient.id,
|
|
'debit': debit,
|
|
'credit': Decimal('0.0'),
|
|
'move': move.id,
|
|
'reference': order.reference,
|
|
'operation_center': order.operation_center.id,
|
|
}]
|
|
account = None
|
|
credit = round(payment.amount, 2)
|
|
if order.copago or order.copago == 0:
|
|
party = order.patient
|
|
account = party.account_receivable_used
|
|
if not config.bill_copago:
|
|
account = config.advance_accounts
|
|
party = order.customer
|
|
else:
|
|
party = order.customer
|
|
account = party.account_receivable_used
|
|
line_add = {
|
|
'description': order.number,
|
|
'party': party.id,
|
|
'account': account,
|
|
'debit': Decimal('0.0'),
|
|
'credit': credit,
|
|
'move': move.id,
|
|
'reference': order.reference,
|
|
}
|
|
lines.append(line_add)
|
|
_lines = MoveLine.create(lines)
|
|
payment.move = move.id
|
|
payment.save()
|
|
Move.post([move])
|
|
return _lines
|
|
|
|
def process_payment(self):
|
|
for pay in self.payments:
|
|
if pay.move:
|
|
continue
|
|
self._create_move_payment(pay)
|
|
|
|
def process_invoice(self, invoice):
|
|
Invoice = Pool().get('account.invoice')
|
|
Invoice.process_invoice([invoice])
|
|
print('Processed invoice...', invoice.number, invoice.electronic_state)
|
|
# if invoice.state == 'draft':
|
|
# Invoice.validate_invoice([invoice])
|
|
# if invoice.state == 'validated':
|
|
# try:
|
|
# if invoice.electronic_state != 'authorized':
|
|
# Invoice.submit([invoice])
|
|
# except Exception as e:
|
|
# print('WARNING: Invoice dont sending to DIAN', e)
|
|
# try:
|
|
# if invoice.invoice_type == 'P' or (
|
|
# invoice.electronic_state in ('authorized', 'accepted')
|
|
# and invoice.state == 'validated'):
|
|
# Invoice.post([invoice])
|
|
# except Exception as e:
|
|
# print('WARNING: invoice dont posted to DIAN', e)
|
|
# self.save()
|
|
|
|
def set_number(self):
|
|
pool = Pool()
|
|
Config = pool.get('laboratory.configuration')
|
|
if self.number:
|
|
return
|
|
config = Config.get_configuration()
|
|
if not config.laboratory_service_order_sequence:
|
|
raise UserError('missing_sequence_service_order')
|
|
|
|
number = config.laboratory_service_order_sequence.get()
|
|
self.write([self], {'number': number})
|
|
|
|
def get_invoice_authorization(self):
|
|
if self.operation_center and self.operation_center.electronic_authorization:
|
|
return self.operation_center.electronic_authorization.id
|
|
|
|
def create_invoice(self):
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
Party = pool.get('party.party')
|
|
Configuration = pool.get('laboratory.configuration')
|
|
config = Configuration(1)
|
|
|
|
_lines = []
|
|
_charges = None
|
|
invoice_for_patient = (self.copago or self.copago == 0) or (self.cuota_moderadora or self.cuota_moderadora == 0)
|
|
if invoice_for_patient and not config.bill_copago:
|
|
return
|
|
if invoice_for_patient:
|
|
party = self.patient
|
|
address = Party.address_get(self.patient, type='invoice')
|
|
payment_term = self.payment_term.id
|
|
_line = self.get_line_order(type='copago_product')
|
|
_lines.append(_line)
|
|
operation_type = '10'
|
|
else:
|
|
operation_type = '10'
|
|
party = self.customer
|
|
address = Party.address_get(self.customer, type='invoice')
|
|
payment_term = self.payment_term.id
|
|
|
|
# Discount rate
|
|
_charges = []
|
|
disc_rate = None
|
|
discount_amount = self.discount_amount
|
|
if discount_amount:
|
|
if not config.account_discount or not config.account_courtesy:
|
|
raise UserError('missing_account_discount')
|
|
total_amount = self.total_amount + (self.delivery if self.delivery else 0)
|
|
if discount_amount == total_amount:
|
|
account = config.account_courtesy
|
|
disc_rate = 100
|
|
else:
|
|
account = config.account_discount
|
|
disc_rate = Decimal(str(round(float(discount_amount / total_amount), 2)))
|
|
disc_rate *= 100
|
|
_charges += [{
|
|
'base_amount': total_amount,
|
|
'amount': discount_amount,
|
|
'description': 'Descuento comercial',
|
|
'charge_percentage': disc_rate,
|
|
'charge_concept': '01',
|
|
'account': account
|
|
}]
|
|
for line in self.lines:
|
|
_line = self.get_line_order(line=line)
|
|
_lines.append(_line)
|
|
if party.sale_invoice_grouping_method == 'standard':
|
|
return
|
|
|
|
if self.delivery and self.delivery > 0:
|
|
_line = self.get_line_order(type='delivery_product')
|
|
_lines.append(_line)
|
|
|
|
Journal = Pool().get('account.journal')
|
|
journals = Journal.search([
|
|
('type', '=', 'revenue'),
|
|
], limit=1)
|
|
journal, = journals
|
|
data = {
|
|
'operation_type': operation_type,
|
|
'company': self.company.id,
|
|
'payment_term': payment_term,
|
|
'reference': self.number,
|
|
'invoice_type': self.invoice_type,
|
|
'currency': 31, # FIXME
|
|
'party': party.id,
|
|
'invoice_date': str(self.order_date),
|
|
'state': 'draft',
|
|
'authorization': self.get_invoice_authorization(),
|
|
'operation_center': self.operation_center.id,
|
|
'invoice_address': address.id,
|
|
'type': 'out',
|
|
'journal': journal.id,
|
|
'account': party.account_receivable_used.id,
|
|
'lines': [('create', _lines)],
|
|
}
|
|
if _charges:
|
|
data.update({'charges': [('create', _charges)]})
|
|
invoice, = Invoice.create([data])
|
|
if invoice_for_patient:
|
|
self.copago_invoice = invoice.id
|
|
self.status_invoice = 'partial_invoice'
|
|
self.save()
|
|
else:
|
|
self.status_invoice = 'invoiced'
|
|
self.invoice = invoice.id
|
|
self.save()
|
|
return invoice
|
|
|
|
def get_line_order(self, line=None, type=None, discount=None):
|
|
pool = Pool()
|
|
Configuration = pool.get('laboratory.configuration')
|
|
config = Configuration(1)
|
|
if type and type == 'copago_product':
|
|
if not config.copago_product:
|
|
raise UserError('missing_product_copago')
|
|
product = config.copago_product
|
|
quantity = 1
|
|
unit_price = self.copago
|
|
elif type and type == 'delivery_product':
|
|
if not config.delivery_product:
|
|
raise UserError('missing_product_delivey')
|
|
product = config.delivery_product
|
|
quantity = 1
|
|
unit_price = self.delivery
|
|
elif line:
|
|
product = line.test
|
|
quantity = line.quantity
|
|
unit_price = line.unit_price
|
|
|
|
str_origin = None
|
|
if line:
|
|
str_origin = 'laboratory.order.line,' + str(line.id)
|
|
|
|
_line = {
|
|
'type': 'line',
|
|
'operation_center': self.operation_center.id,
|
|
'unit': product.template.default_uom.id,
|
|
'product': product.id,
|
|
'quantity': quantity,
|
|
'origin': str_origin,
|
|
'unit_price': unit_price,
|
|
'description': product.rec_name,
|
|
'account': product.template.account_category.account_revenue.id
|
|
}
|
|
|
|
return _line
|
|
|
|
|
|
class CreateInvoiceStart(ModelView):
|
|
'laboratory Create Invoice Start'
|
|
__name__ = 'laboratory.create_invoice.start'
|
|
|
|
start_date = fields.Date('Start Date', required=True)
|
|
end_date = fields.Date('End Date', required=True)
|
|
date_invoice = fields.Date(
|
|
'Date of Invoice', help='if has not date, this date will be today')
|
|
parties = fields.Many2Many(
|
|
'party.party', None, None, 'Parties', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class CreateInvoice(Wizard):
|
|
'laboratory Create Invoice'
|
|
__name__ = 'laboratory.create_invoice'
|
|
|
|
start = StateView('laboratory.create_invoice.start',
|
|
'laboratory.create_invoice_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Generate', 'generate_',
|
|
'tryton-ok', default=True),
|
|
])
|
|
|
|
generate_ = StateTransition()
|
|
|
|
def transition_generate_(self, add_domain=None):
|
|
Order = Pool().get('laboratory.order')
|
|
Invoice = Pool().get('account.invoice')
|
|
Journal = Pool().get('account.journal')
|
|
journal, = Journal.search([('type', '=', 'revenue')], limit=1)
|
|
parties = [party.id for party in self.start.parties]
|
|
|
|
order_domain = [
|
|
('company', '=', self.start.company.id),
|
|
('customer', 'in', parties),
|
|
('order_date', '>=', self.start.start_date),
|
|
('order_date', '<=', self.start.end_date),
|
|
('state', '=', 'processed'),
|
|
('status_invoice', '!=', 'invoiced'),
|
|
('invoice', '=', None),
|
|
]
|
|
order_domain.extend(add_domain) if add_domain else None
|
|
orders = Order.search(
|
|
order_domain,
|
|
order=[('customer', 'ASC'), ('order_date', 'ASC')]
|
|
)
|
|
|
|
for party in set(parties):
|
|
orders_by_party = list(filter(lambda x: x.customer.id == party, orders))
|
|
if not orders_by_party:
|
|
continue
|
|
invoice = self.get_invoice_grouped(orders_by_party, party, journal.id, Invoice)
|
|
Order.write(orders_by_party, {'invoice': invoice.id, 'status_invoice': 'invoiced'})
|
|
|
|
return 'end'
|
|
|
|
def get_invoice_grouped(self, orders, party, journal_id, Invoice):
|
|
customer = orders[0].customer
|
|
address = customer.address_get(type='invoice')
|
|
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'payment_term': customer.customer_payment_term,
|
|
'invoice_type': customer.invoice_type,
|
|
'currency': self.start.company.currency.id,
|
|
'party': customer.id,
|
|
'invoice_date': str(self.start.date_invoice),
|
|
'state': 'draft',
|
|
'authorization': orders[0].get_invoice_authorization(),
|
|
'operation_center': orders[0].operation_center.id,
|
|
'invoice_address': address.id,
|
|
'type': 'out',
|
|
'journal': journal_id,
|
|
'account': customer.account_receivable_used.id,
|
|
}
|
|
configuration = Pool().get('laboratory.configuration')(1)
|
|
Order = Pool().get('laboratory.order')
|
|
lines = []
|
|
for order in orders:
|
|
for line in order.lines:
|
|
_line = order.get_line_order(line=line)
|
|
product_exists = False
|
|
if configuration.group_invoice_lines:
|
|
for line_invoice in lines:
|
|
if line_invoice['product'] == _line['product']:
|
|
line_invoice['quantity'] += _line['quantity']
|
|
product_exists = True
|
|
break
|
|
if not product_exists:
|
|
lines.append(_line)
|
|
else:
|
|
lines.append(_line)
|
|
|
|
data['lines'] = [('create', lines)]
|
|
data['annexes'] = [('add', [order.id for order in orders])]
|
|
invoice, = Invoice.create([data])
|
|
invoice.on_change_annexes()
|
|
invoice.save()
|
|
return invoice
|
|
|
|
|
|
class PrintServiceOrder(Wizard):
|
|
'Print Service Order Report'
|
|
__name__ = 'laboratory.order.print'
|
|
start = StateTransition()
|
|
print_ = StateReport('laboratory.order')
|
|
|
|
def transition_start(self):
|
|
self.services_id = Transaction().context['active_id']
|
|
return 'print_'
|
|
|
|
def do_print_(self, action):
|
|
data = {}
|
|
data['id'] = self.services_ids
|
|
data['ids'] = [data['id']]
|
|
Order = Pool().get('laboratory.order')
|
|
order = Order(data['id'])
|
|
try:
|
|
action['email'] = eval(action['email'])
|
|
except:
|
|
action['email'] = {}
|
|
if order and order.party.email:
|
|
action['email'].update({"to": order.party.email})
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
if self.services_ids:
|
|
return 'print_'
|
|
return 'end'
|
|
|
|
|
|
class OrderLine(DeactivableMixin, Workflow, ModelSQL, ModelView):
|
|
'Service Order Line'
|
|
__name__ = 'laboratory.order.line'
|
|
_rec_name = 'code'
|
|
code = fields.Char('Code', required=True, states=LINE_STATES, depends=['order_state'])
|
|
order = fields.Many2One('laboratory.order', 'Service Order',
|
|
select=True, required=True,
|
|
states={
|
|
'readonly': (
|
|
(Eval('order_state') != 'draft')
|
|
& Bool(Eval('order'))),
|
|
},
|
|
depends=['order_state']
|
|
)
|
|
test = fields.Many2One('product.product', 'Test', required=True,
|
|
states={
|
|
'readonly': ~Eval('order_state').in_(['draft', None])
|
|
}, depends=['order_state'],
|
|
domain=[('salable', '=', True)])
|
|
description = fields.Char('Description', states=LINE_STATES, depends=['order_state'])
|
|
unit_price = fields.Numeric(
|
|
'Unit Price', required=True, states=LINE_STATES, depends=['order_state'])
|
|
quantity = fields.Integer('Qty', required=True, states=LINE_STATES, depends=['order_state'])
|
|
analysis_date = fields.Date('Analysis Date')
|
|
order_state = fields.Function(
|
|
fields.Selection('get_order_states', "Order State"),
|
|
'on_change_with_order_state')
|
|
amount = fields.Function(fields.Numeric('Amount'), 'get_amount')
|
|
authorization = fields.Char('Authorization')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.__access__.add('order')
|
|
|
|
@staticmethod
|
|
def default_quantity():
|
|
return 1
|
|
|
|
def get_amount(self, name=None):
|
|
if self.quantity and self.unit_price:
|
|
return Decimal(self.quantity) * self.unit_price
|
|
|
|
@classmethod
|
|
def get_order_states(cls):
|
|
pool = Pool()
|
|
Laboratory = pool.get('laboratory.order')
|
|
return Laboratory.fields_get(['state'])['state']['selection']
|
|
|
|
@fields.depends('order', '_parent_order.state')
|
|
def on_change_with_order_state(self, name=None):
|
|
if self.order:
|
|
return self.order.state
|
|
|
|
@fields.depends(
|
|
'code', 'test', 'unit_price',
|
|
methods=['compute_unit_price'])
|
|
def on_change_test(self):
|
|
# self.code = None
|
|
# if self.test:
|
|
# self.code = self.test.reference
|
|
# self.unit_price = self.compute_unit_price()
|
|
pool = Pool()
|
|
price_list_id = None
|
|
if self.order.price_list:
|
|
price_list_id = self.order.price_list.id
|
|
self.code = None
|
|
if self.test:
|
|
PriceList = pool.get('product.price_list')
|
|
price_list = PriceList(price_list_id)
|
|
p_list = [line.product for line in price_list.lines]
|
|
if self.test in p_list:
|
|
self.code = self.test.reference
|
|
self.unit_price = self.compute_unit_price()
|
|
else:
|
|
raise UserError('No Esta en la lista de precios')
|
|
else:
|
|
raise UserError('Ya fue agregado el examen')
|
|
else:
|
|
raise UserError('No hay una lista de precios')
|
|
|
|
@fields.depends(
|
|
'test', 'quantity',
|
|
'order', '_parent_order.customer',
|
|
'_parent_order.price_list',
|
|
'_parent_order.order_date', '_parent_order.company')
|
|
def compute_unit_price(self):
|
|
pool = Pool()
|
|
Product = pool.get('product.product')
|
|
|
|
if not self.test:
|
|
return 0
|
|
context = {}
|
|
if self.order:
|
|
if self.order.customer:
|
|
context['customer'] = self.order.customer.id
|
|
if self.order.company:
|
|
context['currency'] = self.order.company.currency.id
|
|
context['company'] = self.order.company.id
|
|
if self.order.price_list:
|
|
context['price_list'] = self.order.price_list.id
|
|
context['sale_date'] = self.order.order_date
|
|
|
|
context['uom'] = self.test.sale_uom.id
|
|
|
|
with Transaction().set_context(context):
|
|
unit_price = Product.get_sale_price([self.test],
|
|
self.quantity or 0)[self.test.id]
|
|
if unit_price:
|
|
unit_price = round_price(unit_price)
|
|
return unit_price
|
|
|
|
|
|
class ServiceOrderReport(Report):
|
|
__name__ = 'laboratory.order.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
Company = Pool().get('company.company')
|
|
context = super(ServiceOrderReport, cls).get_context(records, header,
|
|
data)
|
|
report_id = data['action_id']
|
|
order_receip_id = Id('laboratory', 'service_order_receip').pyson()
|
|
if report_id == order_receip_id:
|
|
type_doc = 'order_receip'
|
|
else:
|
|
type_doc = 'order'
|
|
|
|
context['type_doc'] = type_doc
|
|
context['company'] = Company(Transaction().context.get('company'))
|
|
return context
|
|
|
|
|
|
class OrderPayment(Workflow, ModelSQL, ModelView):
|
|
'Order Payment'
|
|
__name__ = 'laboratory.order.payment'
|
|
_rec_name = 'number'
|
|
number = fields.Char('Number', help='Voucher number')
|
|
order = fields.Many2One('laboratory.order', 'Order', required=True)
|
|
amount = fields.Numeric('Amount', digits=(16, 2), required=True)
|
|
payment_mode = fields.Many2One('account.voucher.paymode', 'Pay Mode',
|
|
required=True)
|
|
payment_date = fields.Date('Payment Date', required=True)
|
|
move = fields.Many2One('account.move', 'Move', states={'readonly': True})
|
|
reconciled = fields.Boolean('Reconciled', states={'readonly': True})
|
|
amount_account = fields.Function(
|
|
fields.Numeric('Amount Account', digits=(16, 2)), 'get_amount_account')
|
|
|
|
def get_amount_account(self, name=None):
|
|
res = []
|
|
if self.move:
|
|
for line in self.move.lines:
|
|
if self.payment_mode.account.id == line.account.id:
|
|
res.append(abs(line.debit - line.credit))
|
|
return sum(res)
|
|
|
|
|
|
class Move(metaclass=PoolMeta):
|
|
__name__ = 'account.move'
|
|
|
|
@classmethod
|
|
def _get_origin(cls):
|
|
models = super()._get_origin()
|
|
models.append('laboratory.order.payment')
|
|
return models
|
|
|
|
|
|
class DetailedBillingAnalysisStart(ModelView):
|
|
'detailed billing analysis Start'
|
|
__name__ = 'laboratory.detailed_billing_analysis.start'
|
|
start_date = fields.Date('Start Date')
|
|
end_date = fields.Date('End Date')
|
|
|
|
|
|
class DetailedBillingAnalysisWizard(Wizard):
|
|
'detailed billing analysis Wizard'
|
|
__name__ = 'laboratory.detailed_billing_analysis'
|
|
start = StateView('laboratory.detailed_billing_analysis.start',
|
|
'laboratory.detailed_billing_analysis_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Ok', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
|
|
print_ = StateReport('laboratory.detailed_billing_analysis.report')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class DetailedBillingAnalysisReport(Report):
|
|
"DetailedBillingAnalysis Report"
|
|
__name__ = 'laboratory.detailed_billing_analysis.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
user = pool.get('res.user')(Transaction().user)
|
|
Invoice = pool.get('account.invoice')
|
|
invoice_dom = [
|
|
('invoice_date', '>=', data['start_date']),
|
|
('invoice_date', '<=', data['end_date']),
|
|
('state', 'not in', ['rejected', 'draft']),
|
|
]
|
|
invoices = Invoice.search(invoice_dom)
|
|
report_context['invoices'] = invoices
|
|
report_context['start_date'] = data['start_date']
|
|
report_context['end_date'] = data['end_date']
|
|
report_context['company'] = user.company
|
|
return report_context
|
|
|
|
|
|
class AddPackageStart(ModelView):
|
|
'Add package Start'
|
|
__name__ = 'laboratory.add_package.start'
|
|
analysis_package = fields.Many2One('laboratory.analysis_package', 'Analysis Package')
|
|
|
|
|
|
class AddPackage(Wizard):
|
|
'Add Package'
|
|
__name__ = 'laboratory.add_package'
|
|
start = StateView(
|
|
'laboratory.add_package.start',
|
|
'laboratory.add_package_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Create Order Line', 'add_package', 'tryton-ok', default=True),
|
|
])
|
|
add_package = StateTransition()
|
|
|
|
def transition_add_package(self):
|
|
pool = Pool()
|
|
active_id = Transaction().context.get('active_id')
|
|
Order = pool.get('laboratory.order')
|
|
OrderLine = pool.get('laboratory.order.line')
|
|
order = Order(active_id)
|
|
customer = self.start.analysis_package.customer
|
|
if order.state == 'processed' or order.state == 'invoiced' or order.state == 'paid':
|
|
raise UserError('La orden ya fue procesada')
|
|
|
|
if customer:
|
|
if order.customer.id != customer.id:
|
|
raise UserError('El paquete seleccionado no pertenece al tercero ' + order.customer.name )
|
|
|
|
print(customer, order.customer.type_document)
|
|
if not customer and order.customer.type_document == '31':
|
|
raise UserError('El paquete seleccionado pertenece terceros con NIT')
|
|
|
|
order_lines = []
|
|
for line in self.start.analysis_package.lines:
|
|
order_line = OrderLine(
|
|
order=active_id,
|
|
code=line.test.reference,
|
|
test=line.test,
|
|
unit_price=line.unit_price,
|
|
quantity=1,
|
|
analysis_date=date.today(),
|
|
description='',
|
|
)
|
|
order_lines.append(order_line)
|
|
OrderLine.save(order_lines)
|
|
Order.save([order])
|
|
|
|
return 'end'
|