trytonpsk-sale_contract/contract.py

762 lines
26 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 *
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
conversor = None
try:
from numword import numword_es
conversor = numword_es.NumWordES()
except:
print("Warning: Does not possible import numword module!")
print("Please install it...!")
__all__ = ['SaleContract', 'SaleContractLine', 'SaleContractReport',
'ContractProductLine', 'SaleContractByMonthStart', 'TypeContract',
'SaleContractByMonth', 'SaleContractByMonthReport',
'CreateInvoicesFromContract', 'CreateInvoicesFromContractStart'
]
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 = [
('', ''),
('C', 'Computador'),
('P', 'POS'),
('M', 'Manual'),
('1', 'Venta Electronica'),
('2', 'Exportacion'),
('3', 'Factura por Contingencia Facturador'),
('4', 'Factura por Contingencia DIAN'),
('91', 'Nota Crédito Eléctronica'),
('92', 'Nota Débito Eléctronica'),
]
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')
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', required=True, states=STATES)
date = fields.Date('Date')
payment_term = fields.Many2One('account.invoice.payment_term',
'Payment Term', required=True, 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=True)
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),
required=True, 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, required=True)
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', required=True, states=STATES)
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',
select=True, states=STATES,)
sales = fields.Function(fields.Many2Many('sale.sale', None, None,
'Sales'), 'get_sales')
invoice_method = fields.Selection([
('manual', 'Manual'),
('order', 'On Order Processed'),
('shipment', 'On Shipment Sent'),
], 'Invoice Method', required=True, select=True)
reference = fields.Char('Reference')
@classmethod
def __setup__(cls):
super(SaleContract, cls).__setup__()
cls._order.insert(0, ('date', 'DESC'))
cls._error_messages.update({
'payment_term_missing': 'The payterm is missing!',
'sequence_missing': 'The sequence is missing!',
'lines_processed': ('Sale Contract "%s" can not deleted '
'because has lines processed'),
'delete_cancel': ('Sale Contract "%s" must be canceled before '
'deletion.'),
'sales_pending': ('You can not to finish contract because '
'has sales pending!'),
})
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 ''
@staticmethod
def default_contract_date():
Date = Pool().get('ir.date')
return Date.today()
@staticmethod
def default_company():
return Transaction().context.get('company') or False
@classmethod
def delete(cls, contracts):
# Cancel before delete
cls.cancel(contracts)
for contract in contracts:
lines = [l for l in contract.lines if l.state in ('processed', 'paid')]
if lines:
cls.raise_user_error('lines_processed', (contract.rec_name,))
if contract.state != 'canceled':
cls.raise_user_error('delete_cancel', (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':
self.raise_user_error('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
'''
pool = Pool()
config = pool.get('sale.configuration')(1)
Sequence = pool.get('ir.sequence')
if self.number:
return
if not config.sale_contract_sequence:
self.raise_user_error('sequence_missing')
seq = config.sale_contract_sequence.id
self.write([self], {'number': Sequence.get_id(seq)})
def get_fee_amount(self, name=None):
return sum([l.unit_price for l in self.product_lines if l.type == 'line' and l.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.sale:
sales_ids.append(line.sale.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):
pass
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)
sale_amount = fields.Numeric('Sale 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)
sale = fields.Many2One('sale.sale', 'Sale', states={
'readonly': True,
})
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',
},
})
@staticmethod
def default_state():
return 'pending'
@classmethod
@ModelView.button
@Workflow.transition('processed')
def process(cls, lines):
for line in lines:
line._create_sale(line.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')
shop_id = Transaction().context.get('shop')
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:
pass
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(contract.party, type='invoice'),
'shipment_address': Party.address_get(contract.party, type='delivery'),
'description': description,
'shop': shop_id if shop_id else None,
'invoice_method': 'order',
'shipment_method': 'order',
}
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_sale_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], {'sale': sale.id})
sale.write([sale], {'contract_line': self.id})
return sale
def get_sale_line(self, contract, line):
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,
'taxes': [],
'description': line.description,
}
taxes_ids = self.get_taxes(contract.party, line.product)
value['taxes'] = [('add', taxes_ids)]
return value
def get_taxes(self, party, product):
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:
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)
return taxes
def validate_contract_line(self):
pass
def _process_sale(self, invoice_type=None, period=None):
Sale = Pool().get('sale.sale')
if not self.sale:
return
if invoice_type:
Sale.write([self.sale], {'invoice_type': invoice_type})
if period:
Sale.write([self.sale], {'reference': period.name})
Sale.quote([self.sale])
if self.contract.invoice_method:
self.sale.invoice_method = self.contract.invoice_method
Sale.confirm([self.sale])
Sale.process([self.sale])
for invoice in self.sale.invoices:
invoice.invoice_date = self.date
invoice.reference = self.sale.reference or ''
invoice.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']
)
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
)
@fields.depends('product')
def on_change_with_unit_price(self):
if self.product:
return self.product.list_price
class SaleContractReport(Report):
__name__ = 'sale_contract.contract_report'
@classmethod
def get_context(cls, records, data):
report_context = super(SaleContractReport, cls).get_context(records, 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',
depends=['fiscalyear'], required=True, 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, data):
report_context = super(SaleContractByMonthReport, cls).get_context(records, 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)
period = fields.Many2One('account.period', 'Period',
depends=['fiscalyear'], required=True, domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
invoice_type = fields.Selection(TYPE_INVOICE, 'Type Invoice')
@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),
],
],],
]
contracts = Contract.search(dom_contract)
lines_to_create = []
date_ = self.start.period.start_date
for contract in contracts:
contract.validate_contract()
day_ = date_.day
if contract.start_date > date_:
day_ = contract.start_date.day
lines_to_create.append({
'contract': contract.id,
'date': date(date_.year, date_.month, day_),
'sale_amount': contract.fee_amount,
'description': contract.description,
'state': 'pending',
})
# date_ = self.start.period.start_date
lines = Line.create(lines_to_create)
for contract_line in lines:
Line.process([contract_line])
if not contract_line.sale:
continue
contract_line.validate_contract_line()
contract_line._process_sale(self.start.invoice_type, self.start.period)
return 'end'
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': '',
# 'salesman': self.start.salesman.id if self.start.salesman else None,
'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 = Contract.create([contract_to_create])
return 'end'