1077 lines
40 KiB
Python
1077 lines
40 KiB
Python
|
||
# This file is part of the sale_weight module for Tryton.
|
||
# The COPYRIGHT file at the top level of this repository contains the full
|
||
# copyright notices and license terms.
|
||
from decimal import Decimal
|
||
from datetime import timedelta, date
|
||
from dateutil.relativedelta import relativedelta
|
||
from trytond.i18n import gettext
|
||
|
||
from trytond.model import Workflow, ModelView, ModelSQL, fields
|
||
from trytond.pool import Pool
|
||
from trytond.pyson import Eval
|
||
from trytond.transaction import Transaction
|
||
from trytond.wizard import (Wizard, StateView, Button,
|
||
StateReport, StateTransition)
|
||
from trytond.report import Report
|
||
from trytond.modules.account_col import invoice
|
||
from trytond.exceptions import UserError
|
||
import calendar
|
||
|
||
conversor = None
|
||
try:
|
||
from numword import numword_es
|
||
conversor = numword_es.NumWordES()
|
||
except Exception:
|
||
print("Warning: Does not possible import numword module!")
|
||
print("Please install it...!")
|
||
|
||
|
||
STATES = {
|
||
'readonly': Eval('state') != 'draft',
|
||
}
|
||
|
||
STATES_LINE = {
|
||
'readonly': Eval('state') != 'pending',
|
||
}
|
||
|
||
_ZERO = Decimal('0.0')
|
||
|
||
RANGE_MONTH = [(str(d + 1), str(d + 1)) for d in range(28)] + [('', '')]
|
||
|
||
TYPE_INVOICE = invoice.TYPE_INVOICE_OUT
|
||
|
||
OPERATION_TYPE_OUT = [
|
||
('', ''),
|
||
('09', 'Servicios AIU'),
|
||
('10', 'Estandar'),
|
||
('11', 'Mandatos bienes'),
|
||
('12', 'Transporte'),
|
||
('13', 'Cambiario'),
|
||
('20', 'Nota Crédito que referencia una factura electrónica'),
|
||
('22', 'Nota Crédito sin referencia a facturas'),
|
||
('30', 'Nota Débito que referencia una factura electrónica'),
|
||
('32', 'Nota Débito sin referencia a facturas'),
|
||
('SS‐CUFE', 'SS‐CUFE'),
|
||
('SS‐CUDE', 'SS‐CUDE'),
|
||
('SS‐POS', 'SS‐POS'),
|
||
('SS‐SNum', 'SS‐SNum'),
|
||
('SS‐Recaudo', 'SS‐Recaudo'),
|
||
('SS‐Reporte', 'SS‐Reporte'),
|
||
('SS‐SinAporte', 'SS‐SinAporte'),
|
||
]
|
||
|
||
|
||
class TypeContract(ModelSQL, ModelView):
|
||
"Type Sale Contract"
|
||
__name__ = 'sale.contract.type'
|
||
name = fields.Char('Name')
|
||
active = fields.Boolean('Active')
|
||
kind = fields.Selection([
|
||
('open', 'Open'),
|
||
('close', 'Close'),
|
||
], 'Kind', select=True)
|
||
invoice_frecuency = fields.Selection([
|
||
('biweekly', 'Biweekly'),
|
||
('monthly', 'Monthly'),
|
||
('annual', 'Annual'),
|
||
('just_one', 'Just One'),
|
||
], 'Invoice Frecuency', select=True, required=True)
|
||
description_template = fields.Char('Description Template')
|
||
|
||
direct_invoice = fields.Boolean('Direct Invoice')
|
||
|
||
|
||
class SaleContract(Workflow, ModelSQL, ModelView):
|
||
"Sale Contract"
|
||
__name__ = 'sale.contract'
|
||
_rec_name = 'number'
|
||
number = fields.Char('Number', readonly=True)
|
||
party = fields.Many2One('party.party', 'Party', required=True,
|
||
select=True, states=STATES)
|
||
contract_date = fields.Date('Contract Date', states={
|
||
'readonly': Eval('state') != 'draft',
|
||
'required': True,
|
||
})
|
||
# date = fields.Date('Date')
|
||
payment_term = fields.Many2One('account.invoice.payment_term',
|
||
'Payment Term', states=STATES)
|
||
lines = fields.One2Many('sale.contract.line', 'contract', 'Lines',
|
||
states={
|
||
'readonly': Eval('state') == 'finished',
|
||
# 'required': Eval('state') == 'finished',
|
||
}, depends=['state'])
|
||
company = fields.Many2One('company.company', 'Company', required=True,
|
||
states=STATES)
|
||
type = fields.Many2One('sale.contract.type', 'Type', states=STATES,
|
||
required=False)
|
||
grouping_invoice = fields.Boolean('Grouping Invoice')
|
||
state = fields.Selection([
|
||
('draft', 'Draft'),
|
||
('confirmed', 'Confirmed'),
|
||
('finished', 'Finished'),
|
||
('canceled', 'Canceled'),
|
||
], 'State', readonly=True, select=True)
|
||
state_string = state.translated('state')
|
||
total_amount = fields.Numeric('Total Amount', digits=(16, 2),
|
||
states={'readonly': True}, depends=['invoices_number'])
|
||
validity_contract = fields.Char('Validity Contract', states=STATES)
|
||
monthly_cutoff_date = fields.Selection(RANGE_MONTH, 'Monthly Cut Off Date',
|
||
states=STATES)
|
||
start_date = fields.Date('Start Date', states=STATES)
|
||
end_date = fields.Date('End Date', states={
|
||
'readonly': Eval('state') != 'draft',
|
||
})
|
||
description = fields.Char('Description', states=STATES, required=True)
|
||
invoices_number = fields.Integer('Invoices Number',
|
||
states={'readonly': Eval('state') != 'draft'})
|
||
product_lines = fields.One2Many('sale.contract.product_line', 'contract',
|
||
'Product Lines', states={
|
||
'required': Eval('state') == 'confirmed',
|
||
'readonly': Eval('state') == 'finished',
|
||
})
|
||
fee_amount = fields.Function(fields.Numeric('Fee Amount'),
|
||
'get_fee_amount')
|
||
total_amount_words = fields.Function(fields.Char('Total Amount Words'),
|
||
'get_total_amount_words')
|
||
comment = fields.Text('Comment', states=STATES)
|
||
salesman = fields.Many2One('company.employee', 'Salesman', states=STATES)
|
||
# origins = fields.Function(fields.Reference('sale.sale', None, None,
|
||
# 'Resources'), 'get_origins')
|
||
invoice_method = fields.Selection([
|
||
('by_balance', 'By Balance'),
|
||
('only_proforma', 'Only Proforma'),
|
||
('standard', 'Standard'),
|
||
], 'Invoice Method', required=True, select=True)
|
||
reference = fields.Char('Reference')
|
||
origin = fields.Reference('Origin', selection='get_origin', select=True,
|
||
states={'readonly': Eval('state') != 'draft'},
|
||
depends=['state'])
|
||
currency = fields.Many2One('currency.currency', 'Currency')
|
||
operation_type = fields.Selection(OPERATION_TYPE_OUT, 'Operation Type')
|
||
# payment_time = fields.Selection([
|
||
# ('monthly', 'Monthly'),
|
||
# ('bimonthly', 'Bimonthly'),
|
||
# ('biannual', 'Biannual'),
|
||
# ('annual', 'annual'),
|
||
# ], 'Payment Time', select=True)
|
||
|
||
@classmethod
|
||
def __setup__(cls):
|
||
super(SaleContract, cls).__setup__()
|
||
cls._order.insert(0, ('contract_date', 'DESC'))
|
||
cls._transitions |= set((
|
||
('draft', 'confirmed'),
|
||
('draft', 'canceled'),
|
||
('confirmed', 'draft'),
|
||
('canceled', 'draft'),
|
||
('confirmed', 'finished'),
|
||
('finished', 'confirmed'),
|
||
))
|
||
cls._buttons.update({
|
||
'create_lines': {
|
||
'invisible': Eval('state') != 'confirmed',
|
||
},
|
||
'cancel': {
|
||
'invisible': Eval('state') != 'draft',
|
||
},
|
||
'draft': {
|
||
'invisible': ~Eval('state').in_(['canceled', 'confirmed']),
|
||
},
|
||
'confirm': {
|
||
'invisible': Eval('state').in_(['canceled', 'confirmed']),
|
||
},
|
||
'finish': {
|
||
'invisible': Eval('state') != 'confirmed',
|
||
},
|
||
})
|
||
|
||
@staticmethod
|
||
def default_state():
|
||
return 'draft'
|
||
|
||
@staticmethod
|
||
def default_invoice_method():
|
||
return 'standard'
|
||
|
||
@staticmethod
|
||
def default_contract_date():
|
||
Date = Pool().get('ir.date')
|
||
return Date.today()
|
||
|
||
@staticmethod
|
||
def default_company():
|
||
return Transaction().context.get('company') or False
|
||
|
||
@staticmethod
|
||
def default_currency():
|
||
Company = Pool().get('company.company')
|
||
company = Transaction().context.get('company')
|
||
if company:
|
||
return Company(company).currency.id
|
||
|
||
@classmethod
|
||
def get_origin(cls):
|
||
Model = Pool().get('ir.model')
|
||
get_name = Model.get_name
|
||
models = cls._get_origin()
|
||
return [(None, '')] + [(m, get_name(m)) for m in models]
|
||
|
||
@classmethod
|
||
def _get_origin(cls):
|
||
'Return list of Model names for origin Reference'
|
||
return ['crm.opportunity']
|
||
|
||
@classmethod
|
||
def delete(cls, contracts):
|
||
# Cancel before delete
|
||
cls.cancel(contracts)
|
||
for contract in contracts:
|
||
lines = [line for line in contract.lines if l.state in ('processed', 'paid')]
|
||
if lines:
|
||
raise UserError(gettext('sale_contract.msg_lines_processed'),
|
||
contract=contract.rec_name)
|
||
if contract.state != 'canceled':
|
||
raise UserError(gettext('sale_contract.delete_cancel'),
|
||
contract=contract.rec_name)
|
||
super(SaleContract, cls).delete(contracts)
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
@Workflow.transition('draft')
|
||
def draft(cls, contracts):
|
||
pass
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
@Workflow.transition('finished')
|
||
def finish(cls, contracts):
|
||
for contract in contracts:
|
||
contract.check_sales()
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
@Workflow.transition('canceled')
|
||
def cancel(cls, contracts):
|
||
pass
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
@Workflow.transition('confirmed')
|
||
def confirm(cls, contracts):
|
||
for contract in contracts:
|
||
contract.set_number()
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
def create_lines(cls, contracts):
|
||
for contract in contracts:
|
||
if contract.type.kind == 'close':
|
||
contract.forecast_sales()
|
||
|
||
def check_sales(self):
|
||
for line in self.lines:
|
||
if line.state == 'pending':
|
||
raise UserError(gettext('sale_contract.sales_pending'))
|
||
|
||
def forecast_sales(self):
|
||
Line = Pool().get('sale.contract.line')
|
||
lines_to_create = []
|
||
for i in range(self.invoices_number):
|
||
date_ = self.start_date + timedelta(30.5 * i)
|
||
lines_to_create.append({
|
||
'contract': self.id,
|
||
'date': date_,
|
||
'sale_amount': self.fee_amount,
|
||
'description': self.description,
|
||
'state': 'pending',
|
||
})
|
||
Line.create(lines_to_create)
|
||
|
||
def set_number(self):
|
||
'''
|
||
Set sequence number
|
||
'''
|
||
config = Pool().get('sale.configuration')(1)
|
||
if self.number:
|
||
return
|
||
if not config.sale_contract_sequence:
|
||
raise UserError(gettext('sale_contract.msg_sequence_missing'))
|
||
number = config.sale_contract_sequence.get()
|
||
self.write([self], {'number': number})
|
||
|
||
def get_fee_amount(self, name=None):
|
||
return sum([
|
||
line.unit_price for line in self.product_lines if line.type == 'line' and line.unit_price
|
||
])
|
||
|
||
def get_total_amount_words(self, name=None):
|
||
if self.total_amount and conversor:
|
||
num = (conversor.cardinal(int(self.total_amount))).upper()
|
||
return num
|
||
|
||
# def get_sales(self, name):
|
||
# sales_ids = []
|
||
# for line in self.lines:
|
||
# if line.origin:
|
||
# sales_ids.append(line.origin.id)
|
||
# return sales_ids
|
||
|
||
@fields.depends('product_lines', 'invoices_number')
|
||
def on_change_with_total_amount(self):
|
||
res = _ZERO
|
||
val_amount = _ZERO
|
||
if self.invoices_number and self.product_lines:
|
||
for line in self.product_lines:
|
||
if not line.unit_price:
|
||
continue
|
||
val_amount += line.unit_price
|
||
res = val_amount * self.invoices_number
|
||
return res
|
||
|
||
def validate_contract(self):
|
||
return True
|
||
|
||
|
||
class SaleContractLine(Workflow, ModelSQL, ModelView):
|
||
"Sale Contract Line"
|
||
__name__ = 'sale.contract.line'
|
||
_rec_name = 'description'
|
||
contract = fields.Many2One('sale.contract', 'Contract', select=True,
|
||
ondelete='CASCADE', states=STATES_LINE)
|
||
date = fields.Date('Date', required=True, states=STATES_LINE)
|
||
amount = fields.Numeric('Amount', digits=(16, 2), required=True,
|
||
states=STATES_LINE)
|
||
state = fields.Selection([
|
||
('pending', 'Pending'),
|
||
('processed', 'Processed'),
|
||
('paid', 'Paid'),
|
||
('canceled', 'Canceled'),
|
||
], 'State', readonly=True, select=True)
|
||
origin = fields.Reference('Origin', selection='get_origin', select=True,
|
||
states=STATES_LINE)
|
||
description = fields.Text('Description', states=STATES_LINE)
|
||
only_first_invoice = fields.Boolean('Only First Invoice',
|
||
states=STATES_LINE)
|
||
|
||
@classmethod
|
||
def __setup__(cls):
|
||
super(SaleContractLine, cls).__setup__()
|
||
cls._transitions |= set((
|
||
('pending', 'processed'),
|
||
('processed', 'canceled'),
|
||
('processed', 'paid'),
|
||
))
|
||
cls._buttons.update({
|
||
'cancel': {
|
||
'invisible': Eval('state') != 'paid'
|
||
},
|
||
'process': {
|
||
'invisible': Eval('state') != 'pending',
|
||
},
|
||
})
|
||
|
||
@classmethod
|
||
def __register__(cls, module_name):
|
||
super(SaleContractLine, cls).__register__(module_name)
|
||
table_h = cls.__table_handler__(module_name)
|
||
cursor = Transaction().connection.cursor()
|
||
if table_h.column_exist('sale'):
|
||
table_h.column_rename('sale_amount', 'amount')
|
||
query = """update sale_contract_line set
|
||
origin=concat('sale.sale,',cast(sale as varchar))
|
||
where sale is not null;"""
|
||
cursor.execute(query)
|
||
table_h.drop_column('sale')
|
||
|
||
@staticmethod
|
||
def default_state():
|
||
return 'pending'
|
||
|
||
@staticmethod
|
||
def default_amount():
|
||
return 0
|
||
|
||
@classmethod
|
||
def _get_origin(cls):
|
||
'Return list of Model names for origin Reference'
|
||
return [
|
||
cls.__name__,
|
||
'sale.sale',
|
||
'account.invoice',
|
||
'account.invoice.line'
|
||
]
|
||
|
||
@classmethod
|
||
def get_origin(cls):
|
||
Model = Pool().get('ir.model')
|
||
get_name = Model.get_name
|
||
models = cls._get_origin()
|
||
return [(None, '')] + [(m, get_name(m)) for m in models]
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
@Workflow.transition('processed')
|
||
def process(cls, lines, product_lines=None, date_start=None, date_end=None):
|
||
for line in lines:
|
||
contract = line.contract
|
||
if contract.type.direct_invoice:
|
||
line._create_invoice(contract, product_lines, date_start, date_end)
|
||
else:
|
||
line._create_sale(contract)
|
||
|
||
@classmethod
|
||
@ModelView.button
|
||
@Workflow.transition('canceled')
|
||
def cancel(cls, lines):
|
||
pass
|
||
|
||
def _create_sale(self, contract):
|
||
pool = Pool()
|
||
Party = pool.get('party.party')
|
||
Sale = pool.get('sale.sale')
|
||
description = contract.description
|
||
if contract.type.description_template and contract.monthly_cutoff_date:
|
||
try:
|
||
start_date = date(
|
||
self.date.year,
|
||
self.date.month,
|
||
int(contract.monthly_cutoff_date))
|
||
end_date = start_date + relativedelta(months=+1)
|
||
description = contract.type.description_template % (start_date, end_date)
|
||
except Exception:
|
||
pass
|
||
party = contract.party
|
||
sale_to_create = {
|
||
'company': contract.company.id,
|
||
'party': contract.party.id,
|
||
'sale_date': self.date,
|
||
'state': 'draft',
|
||
'salesman': contract.salesman.id if contract.salesman else None,
|
||
'invoice_address': Party.address_get(party, type='invoice'),
|
||
'shipment_address': Party.address_get(party, type='delivery'),
|
||
'description': description,
|
||
'invoice_method': 'order',
|
||
'shipment_method': 'order',
|
||
}
|
||
|
||
if hasattr(Sale, 'shop'):
|
||
shop_id = Transaction().context.get('shop')
|
||
sale_to_create['shop'] = shop_id if shop_id else None
|
||
if contract.payment_term:
|
||
sale_to_create['payment_term'] = contract.payment_term.id
|
||
lines_to_create = []
|
||
for line in contract.product_lines:
|
||
value = self.get_line(contract, line)
|
||
lines_to_create.append(value)
|
||
sale_to_create['lines'] = [('create', lines_to_create)]
|
||
sale, = Sale.create([sale_to_create])
|
||
self.write([self], {'origin': str(sale)})
|
||
sale.write([sale], {'contract_line': self.id})
|
||
return sale
|
||
|
||
def get_line(self, contract, line, date_start=None, date_end=None):
|
||
prorate_price = None
|
||
if line.prorate:
|
||
days_prorate = line.days_collect(date_start, date_end)
|
||
if days_prorate < 30:
|
||
prorate_price = line.prorating(days_prorate)
|
||
if line.type == 'subtotal':
|
||
value = {
|
||
'type': line.type,
|
||
'description': line.description,
|
||
}
|
||
else:
|
||
value = {
|
||
'type': line.type,
|
||
'product': line.product.id,
|
||
'quantity': 1,
|
||
'unit': line.product.default_uom.id,
|
||
'unit_price': line.unit_price if not prorate_price else prorate_price,
|
||
'taxes': [],
|
||
'description': line.description,
|
||
}
|
||
taxes_ids = self.get_taxes(contract.party, line)
|
||
value['taxes'] = [('add', taxes_ids)]
|
||
if hasattr(line, 'operation_center'):
|
||
value['operation_center'] = line.operation_center
|
||
return value
|
||
|
||
def get_taxes(self, party, line):
|
||
taxes = []
|
||
pattern = {}
|
||
for tax in line.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:
|
||
for line in party.customer_tax_rule.lines:
|
||
tax_ids = party.customer_tax_rule.apply(line.tax, pattern)
|
||
if tax_ids:
|
||
taxes.extend(tax_ids)
|
||
taxes.extend(line.taxes)
|
||
return taxes
|
||
|
||
def _create_invoice(self, contract, product_lines, start_date_period=None, end_date_period=None):
|
||
pool = Pool()
|
||
Party = pool.get('party.party')
|
||
Invoice = pool.get('account.invoice')
|
||
InvoiceLine = pool.get('account.invoice.line')
|
||
description = contract.description
|
||
if contract.type.description_template and contract.monthly_cutoff_date:
|
||
try:
|
||
start_date = date(
|
||
self.date.year,
|
||
self.date.month,
|
||
int(contract.monthly_cutoff_date))
|
||
end_date = start_date + relativedelta(months=+1)
|
||
description = contract.type.description_template % (start_date, end_date)
|
||
except Exception:
|
||
pass
|
||
party = contract.party
|
||
dpm = str(start_date_period.month) if start_date_period.month >= 10 else '0' + str(start_date_period.month)
|
||
invoice = Invoice(
|
||
company=contract.company.id,
|
||
party=party.id,
|
||
invoice_date=self.date,
|
||
state='draft',
|
||
operation_type=contract.operation_type,
|
||
invoice_type='1',
|
||
salesman=contract.salesman.id if contract.salesman else None,
|
||
invoice_address=Party.address_get(party, type='invoice'),
|
||
description=description,
|
||
account=party.account_receivable.id,
|
||
period=str(start_date_period.year) + '-' + dpm,
|
||
type='out',
|
||
)
|
||
|
||
if hasattr(Invoice, 'shop'):
|
||
shop_id = Transaction().context.get('shop')
|
||
invoice.shop = shop_id if shop_id else None
|
||
if contract.payment_term:
|
||
invoice.payment_term = contract.payment_term.id
|
||
lines_to_create = []
|
||
invoice.on_change_type()
|
||
invoice.on_change_invoice_type()
|
||
invoice.operation_type = contract.operation_type
|
||
invoice.save()
|
||
lines_without_aiu = [ln for ln in product_lines if not ln.relation_aiu]
|
||
for line in lines_without_aiu:
|
||
value = self.get_line(contract, line, start_date_period, end_date_period)
|
||
value['invoice'] = invoice
|
||
value['account'] = line.product.account_revenue_used
|
||
value['note'] = line.note
|
||
value['invoice_type'] = 'out'
|
||
products_aiu = []
|
||
for ln in line.products_aiu:
|
||
value_contract = self.get_line(contract, ln, start_date_period, end_date_period)
|
||
value_contract['invoice'] = invoice
|
||
value_contract['invoice_type'] = 'out'
|
||
value_contract['note'] = ln.note
|
||
value_contract['account'] = ln.product.account_revenue_used
|
||
products_aiu.append(value_contract)
|
||
if products_aiu:
|
||
value['products_aiu'] = [('create', products_aiu)]
|
||
lines_to_create.append(value)
|
||
InvoiceLine.create(lines_to_create)
|
||
invoice.on_change_taxes()
|
||
invoice.save()
|
||
self.write([self], {'origin': str(invoice)})
|
||
return invoice
|
||
|
||
def validate_contract_line(self):
|
||
pass
|
||
|
||
def _process_sale(self, invoice_type=None, period_name=None):
|
||
Sale = Pool().get('sale.sale')
|
||
if not self.origin or self.origin.__name__ != 'sale.sale':
|
||
return
|
||
sale = self.origin
|
||
if invoice_type:
|
||
Sale.write([sale], {'invoice_type': invoice_type})
|
||
if period_name:
|
||
Sale.write([sale], {'reference': period_name})
|
||
|
||
Sale.quote([sale])
|
||
# if self.contract.invoice_method:
|
||
# self.sale.invoice_method = self.contract.invoice_method
|
||
Sale.confirm([sale])
|
||
Sale.process([sale])
|
||
for inv in sale.invoices:
|
||
inv.invoice_date = self.date
|
||
inv.reference = sale.reference or ''
|
||
inv.on_change_invoice_type()
|
||
inv.save()
|
||
|
||
|
||
class ContractProductLine(ModelSQL, ModelView):
|
||
"Contract Product Line"
|
||
__name__ = 'sale.contract.product_line'
|
||
contract = fields.Many2One('sale.contract', 'Contract', select=True,
|
||
ondelete='CASCADE')
|
||
product = fields.Many2One('product.product', 'Product',
|
||
states={
|
||
'invisible': Eval('type') != 'line',
|
||
'required': Eval('type') == 'line',
|
||
},depends=['type'])
|
||
prorate = fields.Boolean('Prorate')
|
||
unit_price = fields.Numeric('Unit Price', digits=(16, 2),
|
||
states={
|
||
'invisible': Eval('type') != 'line',
|
||
'required': Eval('type') == 'line',
|
||
},
|
||
depends=['type'])
|
||
description = fields.Text('Description', required=True)
|
||
only_first_invoice = fields.Boolean('Only First Invoice')
|
||
type = fields.Selection([
|
||
('line', 'Line'),
|
||
('subtotal', 'Subtotal'),
|
||
], 'Type', select=True, required=True)
|
||
taxes = fields.Many2Many(
|
||
'sale.contract-account.tax', 'line', 'tax', 'Taxes',
|
||
order=[
|
||
('tax.sequence', 'ASC'),
|
||
('tax.id', 'ASC')],
|
||
domain=[
|
||
('parent', '=', None),
|
||
[
|
||
'OR',
|
||
('group', '=', None),
|
||
('group.kind', 'in', ['sale', 'both'])],
|
||
('company', '=', Eval('_parent_contract', {}).get('company', -1)),
|
||
],
|
||
states={
|
||
'invisible': Eval('type') != 'line',
|
||
'readonly': ~Eval('contract_state').in_(['draft', None]),
|
||
}, depends=['type', 'contract_state'])
|
||
contract_state = fields.Function(
|
||
fields.Selection('get_contract_states', "Contract State"),
|
||
'on_change_with_contract_state')
|
||
|
||
relation_aiu = fields.Many2One('sale.contract.product_line', 'Relation AIU',
|
||
domain=[
|
||
('contract', '=', Eval('contract')),
|
||
('id', '!=', Eval('id'))
|
||
],
|
||
states={'invisible': Eval('_parent_contract', {}).get('operation_type', '') != '09'},
|
||
depends=['contract'])
|
||
products_aiu = fields.One2Many('sale.contract.product_line', 'relation_aiu', 'Products AIU',
|
||
states={
|
||
'invisible': ~Eval('products_aiu'),
|
||
'readonly': True,
|
||
}
|
||
)
|
||
note = fields.Text('Note')
|
||
start_date = fields.Date('Start Date')
|
||
end_date = fields.Date('End Date')
|
||
|
||
@fields.depends('products_aiu', '_parent_contract.operation_type', 'product')
|
||
def on_change_products_aiu(self):
|
||
if self.contract.operation_type == '09' and self.products_aiu and self.product:
|
||
self.note = 'Contrato de servicios AIU por concepto de: ' + self.product.description
|
||
else:
|
||
self.note = None
|
||
|
||
@fields.depends('contract', '_parent_contract.state')
|
||
def on_change_with_contract_state(self, name=None):
|
||
if self.contract:
|
||
return self.contract.state
|
||
|
||
@classmethod
|
||
def get_contract_states(cls):
|
||
pool = Pool()
|
||
Contract = pool.get('sale.contract')
|
||
return Contract.fields_get(['state'])['state']['selection']
|
||
|
||
@fields.depends('product')
|
||
def on_change_with_unit_price(self):
|
||
if self.product:
|
||
return self.product.list_price
|
||
|
||
def get_rec_name(self, name):
|
||
return self.product.rec_name
|
||
|
||
@staticmethod
|
||
def default_type():
|
||
return 'line'
|
||
|
||
def prorating(self, days):
|
||
base_price = self.unit_price/30
|
||
return round(base_price * days, 2)
|
||
|
||
# def days_collect(self, date):
|
||
# if self.start_date and self.end_date:
|
||
# if self.end_date.month == date.month:
|
||
# date_diference = self.end_date - self.start_date
|
||
# date_diference_days = date_diference.days
|
||
# if date_diference_days <= 30:
|
||
# return date_diference_days
|
||
# else:
|
||
# date_diference = self.end_date - date
|
||
# return date_diference.days
|
||
# if self.start_date > date:
|
||
# date_diference = self.start_date - date
|
||
# return date_diference.days
|
||
|
||
# return 30
|
||
|
||
def days_collect(self, period_start, period_end):
|
||
date_diference = 30
|
||
if self.start_date <= period_start:
|
||
if self.end_date >= period_end:
|
||
return date_diference
|
||
if self.end_date <= period_end:
|
||
total_days_month = calendar.monthrange(period_start.year, period_start.month)[1]
|
||
if total_days_month == 29 and self.end_date.day == 28:
|
||
date_diference = 30
|
||
else:
|
||
date_diference = (self.end_date - period_start).days + 1
|
||
elif self.start_date > period_start and self.end_date >= period_end:
|
||
date_diference = (period_end - self.start_date).days
|
||
elif self.start_date > period_start and self.end_date < period_end:
|
||
date_diference = (self.end_date - self.start_date).days
|
||
return date_diference
|
||
|
||
|
||
class ContractProductTax(ModelSQL):
|
||
"Contract Product Tax"
|
||
__name__ = 'sale.contract-account.tax'
|
||
_table = 'sale_contract_account_tax'
|
||
line = fields.Many2One('sale.contract.product_line', 'Contract Product Tax', ondelete='CASCADE',
|
||
select=True, required=True)
|
||
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
|
||
select=True, required=True)
|
||
|
||
|
||
class SaleContractReport(Report):
|
||
__name__ = 'sale_contract.contract_report'
|
||
|
||
@classmethod
|
||
def get_context(cls, records, header, data):
|
||
report_context = super(SaleContractReport, cls).get_context(records, header, data)
|
||
Company = Pool().get('company.company')
|
||
company_id = Transaction().context.get('company')
|
||
report_context['company'] = Company(company_id)
|
||
return report_context
|
||
|
||
|
||
class SaleContractByMonthStart(ModelView):
|
||
'Sale Contract By Month Start'
|
||
__name__ = 'sale.contract_by_month.start'
|
||
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year', required=True)
|
||
company = fields.Many2One('company.company', 'Company', required=True)
|
||
period = fields.Many2One('account.period', 'Period', required=True,
|
||
depends=['fiscalyear'], domain=[
|
||
('fiscalyear', '=', Eval('fiscalyear')),
|
||
])
|
||
|
||
@staticmethod
|
||
def default_company():
|
||
return Transaction().context.get('company')
|
||
|
||
@fields.depends('fiscalyear')
|
||
def on_change_fiscalyear(self):
|
||
self.period = None
|
||
|
||
@staticmethod
|
||
def default_fiscalyear():
|
||
FiscalYear = Pool().get('account.fiscalyear')
|
||
return FiscalYear.find(
|
||
Transaction().context.get('company'), exception=False)
|
||
|
||
|
||
class SaleContractByMonth(Wizard):
|
||
'Sale Contract By Month'
|
||
__name__ = 'sale.contract_by_month'
|
||
start = StateView('sale.contract_by_month.start',
|
||
'sale_contract.sale_contract_by_month_start_view_form', [
|
||
Button('Cancel', 'end', 'tryton-cancel'),
|
||
Button('Print', 'print_', 'tryton-ok', default=True),
|
||
])
|
||
print_ = StateReport('sale_contract.contract_by_month_report')
|
||
|
||
def do_print_(self, action):
|
||
data = {
|
||
'company': self.start.company.id,
|
||
'fiscalyear': self.start.fiscalyear.id,
|
||
'period': self.start.period.id,
|
||
}
|
||
return action, data
|
||
|
||
def transition_print_(self):
|
||
return 'end'
|
||
|
||
|
||
class SaleContractByMonthReport(Report):
|
||
'Contract By Month Report'
|
||
__name__ = 'sale_contract.contract_by_month_report'
|
||
|
||
@classmethod
|
||
def get_context(cls, records, header, data):
|
||
report_context = super(SaleContractByMonthReport, cls).get_context(
|
||
records, header, data
|
||
)
|
||
pool = Pool()
|
||
SaleContract = pool.get('sale.contract')
|
||
Period = pool.get('account.period')
|
||
period = Period(data['period'])
|
||
|
||
records = SaleContract.search([
|
||
('company', '=', data['company']),
|
||
('contract_date', '>=', period.start_date),
|
||
('contract_date', '<=', period.end_date),
|
||
('state', 'in', ['confirmed', 'finished']),
|
||
])
|
||
|
||
total_amount = []
|
||
for contract in records:
|
||
total_amount.append(contract.total_amount)
|
||
|
||
report_context['sum_total_amount'] = sum(total_amount)
|
||
report_context['period'] = period.name
|
||
report_context['today'] = date.today()
|
||
report_context['records'] = records
|
||
|
||
return report_context
|
||
|
||
|
||
class CreateInvoicesFromContractStart(ModelView):
|
||
'Create Invoices From Contract Start'
|
||
__name__ = 'sale.create_invoices_from_contract.start'
|
||
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year', required=True)
|
||
company = fields.Many2One('company.company', 'Company', required=True)
|
||
party = fields.Many2One('party.party', 'Party')
|
||
period = fields.Many2One('account.period', 'Period',
|
||
depends=['fiscalyear'], required=True, domain=[
|
||
('fiscalyear', '=', Eval('fiscalyear')),
|
||
])
|
||
invoice_type = fields.Selection(TYPE_INVOICE, 'Type Invoice')
|
||
contracts = fields.Many2Many('sale.contract', None, None, 'Contracts',
|
||
domain=[('party', '=', Eval('party')), ('state', '=', 'confirmed')], depends=['party'])
|
||
|
||
@staticmethod
|
||
def default_company():
|
||
return Transaction().context.get('company')
|
||
|
||
@fields.depends('fiscalyear')
|
||
def on_change_fiscalyear(self):
|
||
self.period = None
|
||
|
||
@staticmethod
|
||
def default_fiscalyear():
|
||
FiscalYear = Pool().get('account.fiscalyear')
|
||
return FiscalYear.find(
|
||
Transaction().context.get('company'), exception=False)
|
||
|
||
|
||
class CreateInvoicesFromContract(Wizard):
|
||
'Create Invoices From Contract'
|
||
__name__ = 'sale.create_invoices_from_contract'
|
||
start = StateView('sale.create_invoices_from_contract.start',
|
||
'sale_contract.create_invoices_from_contract_view_form', [
|
||
Button('Cancel', 'end', 'tryton-cancel'),
|
||
Button('Create', 'accept', 'tryton-ok', default=True),
|
||
])
|
||
accept = StateTransition()
|
||
|
||
def transition_accept(self):
|
||
pool = Pool()
|
||
Contract = pool.get('sale.contract')
|
||
Line = Pool().get('sale.contract.line')
|
||
lines = Line.search([
|
||
('contract.state', '=', 'confirmed'),
|
||
('date', '>=', self.start.period.start_date),
|
||
('date', '<=', self.start.period.end_date),
|
||
])
|
||
lines_invoiced = []
|
||
if lines:
|
||
lines_invoiced = [line.contract.id for line in lines]
|
||
dom_contract = [
|
||
['AND', [
|
||
'OR', [
|
||
('type.kind', '=', 'open'),
|
||
('state', '=', 'confirmed'),
|
||
('end_date', '>=', self.start.period.start_date),
|
||
], [
|
||
('type.kind', '=', 'open'),
|
||
('state', '=', 'confirmed'),
|
||
('end_date', '=', None),
|
||
],
|
||
]],
|
||
]
|
||
|
||
if lines_invoiced:
|
||
dom_contract = [
|
||
['AND', [
|
||
'OR', [
|
||
('type.kind', '=', 'open'),
|
||
('state', '=', 'confirmed'),
|
||
('end_date', '>=', self.start.period.start_date),
|
||
('id', 'not in', lines_invoiced),
|
||
], [
|
||
('type.kind', '=', 'open'),
|
||
('state', '=', 'confirmed'),
|
||
('end_date', '=', None),
|
||
('id', 'not in', lines_invoiced),
|
||
],
|
||
]],
|
||
]
|
||
if self.start.party:
|
||
dom_contract.append(('party', '=', self.start.party))
|
||
if self.start.contracts:
|
||
contract_ids = [contract.id for contract in self.start.contracts]
|
||
dom_contract.append(('id', 'in', contract_ids))
|
||
|
||
contracts = Contract.search(dom_contract)
|
||
lines_to_create = []
|
||
period_start_date = self.start.period.start_date
|
||
period_end_date = self.start.period.end_date
|
||
for contract in contracts:
|
||
validate_dates = None
|
||
lines_to_products = []
|
||
original_lines = []
|
||
for product in contract.product_lines:
|
||
validate_dates = self.validate_dates(product, self.start.period)
|
||
if validate_dates:
|
||
validate = contract.validate_contract()
|
||
if validate:
|
||
day_ = period_start_date.day
|
||
prorate_price = None
|
||
if contract.start_date > period_start_date:
|
||
day_ = contract.start_date.day
|
||
if product.prorate:
|
||
days_prorate = product.days_collect(period_start_date, period_end_date)
|
||
if days_prorate < 30:
|
||
prorate_price = product.prorating(days_prorate)
|
||
lines_to_create.append({
|
||
'contract': contract.id,
|
||
'date': date(period_start_date.year, period_start_date.month, day_),
|
||
'amount': product.unit_price if not prorate_price else prorate_price,
|
||
'description': product.description,
|
||
'state': 'pending',
|
||
})
|
||
lines_to_products.append(product)
|
||
lines = Line.create(lines_to_create)
|
||
lines_to_create = []
|
||
original_lines.extend(lines)
|
||
if not contract.grouping_invoice:
|
||
for contract_line in lines:
|
||
if not product.relation_aiu:
|
||
Line.process([contract_line], [product], period_start_date)
|
||
if not contract_line.origin or (contract_line.origin and contract_line.origin.__name__ == 'account.invoice'):
|
||
continue
|
||
contract_line.validate_contract_line()
|
||
contract_line._process_sale(
|
||
self.start.invoice_type, self.start.period.name
|
||
)
|
||
if len(lines_to_products) > 0:
|
||
invoice_tmp = original_lines[0]._create_invoice(contract, lines_to_products, period_start_date, period_end_date)
|
||
for contract_line in original_lines:
|
||
if not contract_line.origin:
|
||
contract_line.origin = str(invoice_tmp)
|
||
contract_line.save()
|
||
return 'end'
|
||
|
||
def validate_dates(self, product, period):
|
||
if product.start_date and product.end_date:
|
||
if product.start_date >= period.start_date and product.end_date <= period.end_date:
|
||
return True
|
||
if product.start_date > period.end_date:
|
||
return False
|
||
elif product.start_date <= period.end_date and not product.end_date:
|
||
return True
|
||
elif product.end_date >= period.start_date and product.end_date <= period.end_date:
|
||
return True
|
||
elif product.end_date >= period.end_date:
|
||
return True
|
||
else:
|
||
False
|
||
if product.start_date and not product.end_date:
|
||
if product.start_date < period.end_date:
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
def same_month(self, date1, date2):
|
||
return date1.year == date2.year and date1.month == date2.month
|
||
|
||
|
||
class SaleContractFromPartyStart(ModelView):
|
||
'Create Sale Contract From Parties Start'
|
||
__name__ = 'sale.create_contract_from_party.start'
|
||
start_date = fields.Date('Start Date', required=True)
|
||
end_date = fields.Date('End Date',)
|
||
payment_term = fields.Many2One('account.invoice.payment_term',
|
||
'Payment Term', required=True)
|
||
parties = fields.One2Many('party.party', None, 'Parties', add_remove=[],
|
||
required=True)
|
||
products = fields.One2Many('product.product', None, 'Products',
|
||
add_remove=[], required=True)
|
||
# salesman = fields.Many2One('company.employee', 'Salesman')
|
||
company = fields.Many2One('company.company', 'Company', required=True)
|
||
type_contract = fields.Many2One('sale.contract.type', 'Type Contract',
|
||
required=True)
|
||
cuttof_day = fields.Numeric('cuttof_day')
|
||
|
||
@staticmethod
|
||
def default_company():
|
||
return Transaction().context.get('company')
|
||
|
||
@fields.depends('start_date', 'end_date')
|
||
def on_change_start_date(self):
|
||
if self.start_date:
|
||
self.end_date = self.start_date + timedelta(days=365)
|
||
|
||
|
||
class SaleContractFromParty(Wizard):
|
||
'Create Contract From Parties'
|
||
__name__ = 'sale.create_contract_from_party'
|
||
start = StateView('sale.create_contract_from_party.start',
|
||
'sale_contract.create_contract_from_party_start_view_form', [
|
||
Button('Cancel', 'end', 'tryton-cancel'),
|
||
Button('Create', 'accept', 'tryton-ok', default=True),
|
||
])
|
||
accept = StateTransition()
|
||
|
||
def transition_accept(self):
|
||
pool = Pool()
|
||
Contract = pool.get('sale.contract')
|
||
Date = pool.get('ir.date')
|
||
|
||
for party in self.start.parties:
|
||
contract_to_create = {
|
||
'company': self.start.company.id,
|
||
'party': party.id,
|
||
'contract_date': self.start.start_date or Date.today(),
|
||
'payment_term': self.start.payment_term.id or None,
|
||
'type': self.start.type_contract.id,
|
||
'start_date': self.start.start_date,
|
||
'end_date': self.start.end_date or None,
|
||
'comment': '',
|
||
'total_amount': 1,
|
||
'description': ' ',
|
||
'invoices_number': 1,
|
||
'invoice_method': 'order',
|
||
'state': 'draft',
|
||
'product_lines': [],
|
||
'monthly_cutoff_date': self.start.cuttof_day or None,
|
||
}
|
||
|
||
lines_to_create = []
|
||
for product in self.start.products:
|
||
value = {
|
||
# 'analytic_account': None,
|
||
'description': product.description or product.name,
|
||
'product': product.id,
|
||
'type': 'line',
|
||
'unit_price': product.list_price,
|
||
'only_first_invoice': False,
|
||
}
|
||
lines_to_create.append(value)
|
||
contract_to_create['product_lines'] = [('create', lines_to_create)]
|
||
Contract.create([contract_to_create])
|
||
return 'end'
|