trytonpsk-purchase_co/purchase.py
2023-12-13 17:37:43 -05:00

683 lines
25 KiB
Python

# 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, timedelta
from decimal import Decimal
from trytond.pyson import Eval, Not, Bool
from trytond.pool import PoolMeta, Pool
from trytond.model import fields, ModelView
from trytond.exceptions import UserError
from trytond.transaction import Transaction
from trytond.report import Report
from trytond.wizard import (
Wizard, StateView, Button, StateReport, StateTransition
)
class Configuration(metaclass=PoolMeta):
__name__ = 'purchase.configuration'
reference_required = fields.Boolean('Reference Required')
all_warehouse = fields.Boolean('All Warehouse',
help='See quantity in all warehouse')
class Party(metaclass=PoolMeta):
__name__ = 'party.party'
msm_cerfificate_supplier = fields.Char('msm Cerfificate Supplier')
class Purchase(metaclass=PoolMeta):
__name__ = 'purchase.purchase'
iva = fields.Function(fields.Numeric('IVA'), 'get_tax_grouped')
ica = fields.Function(fields.Numeric('ICA'), 'get_tax_grouped')
ret = fields.Function(fields.Numeric('RET'), 'get_tax_grouped')
@classmethod
def __setup__(cls):
super(Purchase, cls).__setup__()
cls.state_string = super(Purchase, cls).state.translated('state')
cls._buttons.update({
'wizard_generate_invoice': {
'invisible': Eval('state').in_(['done', 'processing', 'cancel']),
'readonly': Not(Bool(Eval('lines'))),
},
})
cls._states_cached = ['confirmed', 'done', 'cancel']
@classmethod
@ModelView.button
def wizard_generate_invoice(cls, purchases):
cls.generate_invoice(purchases)
@classmethod
def generate_invoice(cls, purchases):
_purchases = cls.process_purchases(purchases)
cls.post_invoices(_purchases)
@classmethod
def generate_shipment(cls, purchases):
res = [pu for pu in purchases if pu.state == 'processing']
cls.do_shipment(res)
@classmethod
def process_purchases(cls, purchases):
res = []
for purchase in purchases:
if purchase.state == 'draft':
cls.quote([purchase])
if purchase.state == 'quotation':
cls.confirm([purchase])
if purchase.state == 'confirmed':
cls.process([purchase])
res.append(purchase)
return res
@classmethod
def post_invoices(cls, purchases):
Invoice = Pool().get('account.invoice')
to_post = []
for purchase in purchases:
for invoice in purchase.invoices:
invoice.reference = purchase.reference
invoice.description = purchase.description
_invoice_date = invoice.invoice_date or purchase.purchase_date
invoice.accounting_date = purchase.purchase_date
if invoice.state == 'draft':
if not getattr(invoice, 'invoice_date', False):
invoice.invoice_date = purchase.purchase_date
if not getattr(invoice, 'accounting_date', False):
invoice.accounting_date = _invoice_date or date.today()
to_post.append(invoice)
Invoice.post(to_post)
@classmethod
def do_shipment(cls, purchases):
for purchase in purchases:
purchase.create_shipment('in')
purchase.set_shipment_state()
@classmethod
def update_state(cls, purchases):
for purchase in purchases:
if purchase.is_done():
cls.do([purchase])
def create_shipment(self, shipment_type):
'''
Create and return shipments of type shipment_type
'''
pool = Pool()
Shipment = pool.get('stock.shipment.in')
moves = []
for line in self.lines:
if line.moves:
moves.extend([l for l in line.moves if not l.shipment])
shipment = Shipment(
warehouse=self.warehouse.id,
supplier=self.party.id,
company=self.company.id,
)
shipment.moves = (list(getattr(shipment, 'moves', [])) + moves)
shipment.reference = self.reference
shipment.save()
@classmethod
def get_amount(cls, purchases, names):
result = super(Purchase, cls).get_amount(purchases, names)
Product = Pool().get('product.product')
keys = result.keys()
for purchase in purchases:
res = Product.get_amount_with_extra_tax(purchase.lines)
if 'tax_amount' in keys:
if purchase.state in ('draft', 'processing'):
result['tax_amount'][purchase.id] += res
if result.get('total_amount'):
result['total_amount'][purchase.id] += res
else:
purchase.tax_amount_cache = result['tax_amount'][purchase.id] + res
if result.get('total_amount'):
result['total_amount'][purchase.id] += res
return result
def taxes(self):
return self._get_taxes()
def get_total_iva(self, name=None):
return self.untaxed_amount + self.iva
def get_tax_grouped(self, name=None):
""" For use in purchase report"""
res = []
Tax = Pool().get('account.tax')
taxes = self.taxes()
for value in taxes.values():
tax = Tax(value['tax'])
if tax.classification == name:
res.append(value['amount'])
return sum(res)
def create_invoice(self):
invoice = super(Purchase, self).create_invoice()
if not invoice:
return
invoice.reference = self.reference
invoice.description = self.description
if self.invoice_method == 'shipment' and self.moves:
for move in self.moves:
invoice.reference = move.shipment.reference
break
invoice.save()
return invoice
@classmethod
def process(cls, purchases):
for rec in purchases:
if not rec.reference:
raise UserError('El campo referencia es obligatorio!')
super(Purchase, cls).process(purchases)
@classmethod
def quote(cls, purchases):
cls.store_cache(purchases)
pool = Pool()
Configuration = pool.get('purchase.configuration')
config = Configuration(1)
if config.reference_required:
cls.check_duplicated(purchases)
super(Purchase, cls).quote(purchases)
@classmethod
def check_duplicated(cls, purchases):
today = date.today()
target_date = today - timedelta(days=90)
for purchase in purchases:
duplicates = cls.search_read([
('reference', '=', purchase.reference),
('party', '=', purchase.party.id),
('purchase_date', '>=', target_date),
], fields_names=['reference'])
if len(duplicates) >= 2:
raise UserError('Al parecer esta compra esta duplicada!')
def _get_invoice_purchase(self):
'Return invoice'
invoice = super(Purchase, self)._get_invoice_purchase()
if self.reference and hasattr(invoice, 'reference') and not getattr(invoice, 'reference'):
invoice.reference = self.reference
return invoice
@classmethod
def delete(cls, purchases):
for purchase in purchases:
if purchase.number:
raise UserError(
'No es posible eliminar compras que ya tengan asignado un consecutivo',
)
super(Purchase, cls).delete(purchases)
class Line(metaclass=PoolMeta):
__name__ = 'purchase.line'
stock_quantity = fields.Function(fields.Float('Stock Quantity',
digits=(16, Eval('default_uom_digits', 2))),
'on_change_with_stock_quantity')
# date_start = fields.Date('Date Start')
# date_end = fields.Date('Date End')
party = fields.Function(fields.Many2One('party.party', 'Party'), 'get_parent_data')
date = fields.Function(fields.Date('Date'), 'get_parent_data')
def get_parent_data(self, name=None):
if name == 'date':
return self.purchase.purchase_date
if name == 'party':
return self.purchase.party.id
@fields.depends('product', 'purchase',
'_parent_purchase.warehouse')
def on_change_with_stock_quantity(self, name=None):
if self.product:
context = {'stock_date_end': date.today()}
Location = Pool().get('stock.location')
Configuration = Pool().get('purchase.configuration')
configuration = Configuration(1)
if configuration.all_warehouse:
locations = Location.search([
('type', '=', 'warehouse')
])
location_ids = [l.storage_location.id for l in locations if l.storage_location]
elif self.purchase.warehouse:
location_ids = [self.purchase.warehouse.storage_location.id]
else:
return 0
product_ids = [self.product.id]
quantity = 0
with Transaction().set_context(context):
Product = Pool().get('product.product')
pbl = Product.products_by_location(
location_ids,
grouping=('product', ),
grouping_filter=(product_ids,))
for v in pbl.values():
quantity += v
quantity = quantity
return quantity
@fields.depends('description')
def on_change_product(self):
super(Line, self).on_change_product()
if self.product:
self.description = self.product.name
else:
self.description = None
class PurchaseForceDraft(Wizard):
'Purchase Force Draft'
__name__ = 'purchase.purchase.force_draft'
start_state = 'force_draft'
force_draft = StateTransition()
@classmethod
def __setup__(cls):
super(PurchaseForceDraft, cls).__setup__()
def transition_force_draft(self):
Purchase = Pool().get('purchase.purchase')
Invoice = Pool().get('account.invoice')
ids = Transaction().context['active_ids']
if ids:
purchase = Purchase(ids[0])
number_invoices = []
if purchase.invoices:
number_invoices = [invoice.number for invoice in purchase.invoices if invoice.number]
if number_invoices:
msg = 'No puede enviar la factura a borrador porque ya tiene un número de factura generado!'
raise UserError(msg)
return 'end'
if purchase.shipments:
number_ship = [shipment.number for shipment in purchase.shipments if shipment.number]
if number_ship:
msg = 'No puede enviar la factura a borrador porque ya tiene un número de envio!'
raise UserError(msg)
return 'end'
Invoice.delete(purchase.invoices)
edit_field = {}
if hasattr(purchase, 'approval_date'):
edit_field = {
'confirmation_date': None
}
if hasattr(purchase, 'confirmation_date'):
edit_field = {
'confirmation_date': None
}
Purchase.write([purchase], {'state': 'draft', **edit_field})
return 'end'
class PurchaseAnalyticStart(ModelView):
'Purchase Analytic Report Start'
__name__ = 'purchase_co.analytic.start'
company = fields.Many2One('company.company', 'Company', required=True)
start_date = fields.Date("Start Date", required=True)
end_date = fields.Date("End Date", required=True)
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_end_date():
Date = Pool().get('ir.date')
return Date.today()
class PurchaseAnalytic(Wizard):
'Purchase Analytic Report'
__name__ = 'purchase_co.analytic'
start = StateView('purchase_co.analytic.start',
'purchase_co.purchase_analytic_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('purchase_co.analytic.report')
def do_print_(self, action):
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
}
return action, data
def transition_print_(self):
return 'end'
class PurchaseAnalyticReport(Report):
__name__ = 'purchase_co.analytic.report'
@classmethod
def compute_amount_tax(cls, line):
Tax = Pool().get('account.tax')
tax_list = Tax.compute(Tax.browse(line['taxes']),
line['unit_price'] or Decimal('0.0'),
line['quantity'] or 0.0)
return sum([t['amount'] for t in tax_list], Decimal('0.0'))
@classmethod
def _get_rec(cls, line):
analytic_account = None
if line['analytic_accounts.']:
analytic_account = line['analytic_accounts.'][0]['account.']
inv_unit_price = Decimal(0)
if line['invoice_lines.']:
inv_unit_price = line['invoice_lines.'][0]['unit_price']
value = {
'reference': line['purchase.']['reference'],
'purchase_date': line['purchase.']['purchase_date'],
'state': line['purchase.']['state'],
'shipment_state': line['purchase.']['shipment_state'],
'invoice_state': line['purchase.']['invoice_state'],
'id_number': line['purchase.']['party.']['id_number'],
'name': line['purchase.']['party.']['name'],
'warehouse': line['purchase.']['warehouse.']['name'],
'description': line['description'],
'unit_name': line['unit.']['name'],
'quantity': line['quantity'],
'unit_price': line['unit_price'],
'analytic_account': analytic_account['code'] + ' ' + analytic_account['name'] if analytic_account else '',
'taxes': list(line['taxes']),
'qty_received': sum([r['quantity'] for r in line['moves.']]),
'amount': line['amount'],
'inv_unit_price': inv_unit_price,
'number': line['purchase.']['number']
}
tax_amount = cls.compute_amount_tax(value)
full_amount = value['amount'] + tax_amount
value.update({
'tax_amount': tax_amount,
'full_amount': full_amount,
})
return value
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
PurchaseLine = pool.get('purchase.line')
fields_names = [
'purchase.reference', 'purchase.purchase_date',
'purchase.party.id_number', 'purchase.party.name', 'description',
'unit.name', 'quantity', 'unit_price', 'purchase.state',
'purchase.shipment_state', 'purchase.invoice_state',
'analytic_accounts.account.name', 'analytic_accounts.account.code',
'taxes', 'invoice_lines.unit_price', 'moves.quantity', 'amount',
'purchase.warehouse.name', 'purchase.number'
]
lines = PurchaseLine.search_read([
('purchase.company', '=', data['company']),
('purchase.purchase_date', '>=', data['start_date']),
('purchase.purchase_date', '<=', data['end_date']),
], fields_names=fields_names, order=[('purchase.purchase_date', 'ASC')])
records = []
records_append = records.append
get_rec = cls._get_rec
for line in lines:
records_append(get_rec(line))
report_context['records'] = records
report_context['company'] = Company(data['company'])
return report_context
class PurchasesDetailedStart(ModelView):
'Purchases Detailed Start'
__name__ = 'purchase.purchases_detailed.start'
company = fields.Many2One('company.company', 'Company', required=True)
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
invoiced = fields.Boolean('Invoiced', help='Print purchase invoiced')
detailed = fields.Boolean('Detailed', help='Print report detailed')
@staticmethod
def default_company():
return Transaction().context.get('company')
class PurchasesDetailed(Wizard):
'Purchases Detailed'
__name__ = 'purchase.purchases_detailed'
start = StateView('purchase.purchases_detailed.start',
'purchase_co.print_purchases_detailed_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('purchase.purchases_detailed.report')
def do_print_(self, action):
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'invoiced': self.start.invoiced,
'detailed': self.start.detailed,
}
return action, data
def transition_print_(self):
return 'end'
class PurchasesDetailedReport(Report):
__name__ = 'purchase.purchases_detailed.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Invoice = pool.get('account.invoice')
Purchase = pool.get('purchase.purchase')
PurchaseLine = pool.get('purchase.line')
InvoiceLine = pool.get('account.invoice.line')
Company = pool.get('company.company')
fields_names = [
'number', 'description', 'party.name', 'untaxed_amount',
'party.id_number', 'state', 'tax_amount', 'total_amount',
'currency.code'
]
fields_names_detailed = [
'product.rec_name', 'product.type', 'product.categories.name', 'quantity', 'unit_price',
]
fields = Purchase.fields_get(fields_names=['operation_center'])
if data['invoiced']:
title = 'INFORME DE COMPRAS'
if 'operation_center' in fields.keys():
fields_names.append('purchases.operation_center.name')
fields_names_detailed.append('invoice.purchases.operation_center.name')
if data['detailed']:
fields_add = ['invoice.party.name', 'invoice.party.id_number', 'invoice.purchases.number', 'invoice.purchases.purchase_date']
fields_names_detailed.extend(fields_add)
dom_ = [
('invoice.company', '=', data['company']),
('invoice.invoice_date', '>=', data['start_date']),
('invoice.invoice_date', '<=', data['end_date']),
('invoice.type', '=', 'in'),
('invoice.purchases', '!=', None),
]
records = InvoiceLine.search_read(dom_, fields_names=fields_names_detailed)
else:
fields_add = ['invoice_date', 'purchases.description', 'purchases.number']
fields_names.extend(fields_add)
dom_ = [
('company', '=', data['company']),
('invoice_date', '>=', data['start_date']),
('invoice_date', '<=', data['end_date']),
('type', '=', 'in'),
('purchases', '!=', None),
]
records = Invoice.search_read(dom_, fields_names=fields_names)
else:
title = 'INFORME DE ORDENES DE COMPRAS'
if 'operation_center' in fields.keys():
fields_names_detailed.append('purchase.operation_center.name')
fields_names.append('operation_center.name')
if data['detailed']:
fields_add = [
'purchase.party.name', 'purchase.party.id_number',
'purchase.number', 'purchase.purchase_date']
fields_names_detailed.extend(fields_add)
dom_ = [
('purchase.company', '=', data['company']),
('purchase.purchase_date', '>=', data['start_date']),
('purchase.purchase_date', '<=', data['end_date']),
]
records = PurchaseLine.search_read(dom_, fields_names=fields_names_detailed)
else:
fields_names.append('purchase_date')
dom_purchases = [
('company', '=', data['company']),
('purchase_date', '>=', data['start_date']),
('purchase_date', '<=', data['end_date']),
]
records = Purchase.search_read(dom_purchases,
fields_names=fields_names, order=[
('party.name', 'ASC'),
('purchase_date', 'ASC')
])
states = {
'posted': 'contabilizado',
'paid': 'pagada',
'draft': 'borrador',
'done': 'finalizado',
'processing': 'procesada',
'quotation': 'cotizacion',
}
report_context['records'] = records
report_context['types'] = {'goods': 'bienes', 'service': 'servicios', 'assets': 'activos'}
report_context['states'] = states
report_context['company'] = Company(data['company'])
report_context['data'] = data
report_context['title'] = title
return report_context
class PurchaseGenerateInvoice(Wizard):
"""Purchase Generate Invoice."""
__name__ = 'purchase.purchase.generate_invoice'
start_state = 'generate_invoice'
generate_invoice = StateTransition()
def transition_generate_invoice(self):
Purchase = Pool().get('purchase.purchase')
ids = Transaction().context['active_ids']
if ids:
purchase = Purchase(ids[0])
# if purchase.state not in ('draft', 'quotation', 'confirmed'):
if purchase.invoices:
raise UserError('No puede generar una factura porque ya ha sido generada!')
purchase.generate_invoice([purchase])
return 'end'
class PurchaseGenerateShipment(Wizard):
"""
Purchase Generate Shipment
"""
__name__ = 'purchase.purchase.generate_shipment'
start_state = 'generate_shipment'
generate_shipment = StateTransition()
@classmethod
def __setup__(cls):
super(PurchaseGenerateShipment, cls).__setup__()
def transition_generate_shipment(self):
Purchase = Pool().get('purchase.purchase')
ids = Transaction().context['active_ids']
if ids:
purchase = Purchase(ids[0])
if purchase.state in ('cancelled', 'done'):
raise UserError('No puede generar el envio!')
purchase.generate_shipment([purchase])
return 'end'
class PurchaseUpdateStart(ModelView):
'Purchase Update Start'
__name__ = 'purchase.update.start'
date = fields.Date('Date')
description = fields.Char('Description')
tax_add = fields.Many2One('account.tax', 'Add Tax', domain=[
('group.kind', '=', Eval('group_tax'))
], depends=['group_tax'])
tax_remove = fields.Many2One('account.tax', 'Remove Tax', domain=[
('group.kind', '=', Eval('group_tax'))
], depends=['group_tax'])
group_tax = fields.Char('Group Tax')
@staticmethod
def default_group_tax():
return 'purchase'
class PurchaseUpdate(Wizard):
'Purchase Update'
__name__ = 'purchase.update'
start = StateView('purchase.update.start',
'purchase_co.purchase_update_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'accept', 'tryton-ok', default=True),
])
accept = StateTransition()
def _update_all_dates(self):
pass
def transition_accept(self):
Purchase = Pool().get('purchase.purchase')
Line = Pool().get('purchase.line')
purchases = Purchase.browse(Transaction().context['active_ids'])
values = {}
if self.start.date:
values['purchase_date'] = self.start.date
if self.start.description:
values['description'] = self.start.description
purchases = [p for p in purchases if p.state == 'draft']
if values:
Purchase.write(purchases, values)
if (self.start.tax_add or self.start.tax_remove) and purchases:
purchase = purchases[0]
lines_to_change = []
for line in purchase.lines:
if line.type != 'line':
continue
lines_to_change.append(line)
if lines_to_change:
if self.start.tax_add:
Line.write(lines_to_change, {'taxes': [
('add', [self.start.tax_add.id])]})
if self.start.tax_remove:
Line.write(lines_to_change, {'taxes': [
('remove', [self.start.tax_remove.id])]})
purchase.save()
return 'end'