trytonpsk-purchase_co/purchase.py

684 lines
25 KiB
Python
Raw Permalink Normal View History

2021-09-10 17:03:43 +02:00
# 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
2022-10-31 16:57:01 +01:00
from trytond.pyson import Eval, Not, Bool
2021-09-10 17:03:43 +02:00
from trytond.pool import PoolMeta, Pool
from trytond.model import fields, ModelView
2021-09-10 17:03:43 +02:00
from trytond.exceptions import UserError
from trytond.transaction import Transaction
from trytond.report import Report
2022-10-14 22:13:41 +02:00
from trytond.wizard import (
Wizard, StateView, Button, StateReport, StateTransition
)
2021-09-10 17:03:43 +02:00
class Configuration(metaclass=PoolMeta):
__name__ = 'purchase.configuration'
reference_required = fields.Boolean('Reference Required')
2022-10-14 22:13:41 +02:00
all_warehouse = fields.Boolean('All Warehouse',
help='See quantity in all warehouse')
2021-09-10 17:03:43 +02:00
class Party(metaclass=PoolMeta):
__name__ = 'party.party'
msm_cerfificate_supplier = fields.Char('msm Cerfificate Supplier')
2021-09-10 17:03:43 +02:00
class Purchase(metaclass=PoolMeta):
__name__ = 'purchase.purchase'
2022-03-17 18:27:44 +01:00
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')
2021-09-10 17:03:43 +02:00
2022-03-17 18:27:44 +01:00
@classmethod
def __setup__(cls):
super(Purchase, cls).__setup__()
cls.state_string = super(Purchase, cls).state.translated('state')
2022-10-31 16:57:01 +01:00
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:
2022-11-03 20:38:50 +01:00
moves.extend([l for l in line.moves if not l.shipment])
2022-10-31 16:57:01 +01:00
shipment = Shipment(
warehouse=self.warehouse.id,
2022-11-03 20:38:50 +01:00
supplier=self.party.id,
company=self.company.id,
2022-10-31 16:57:01 +01:00
)
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
2021-09-27 19:28:40 +02:00
2022-03-17 15:50:06 +01:00
def taxes(self):
return self._get_taxes()
2022-03-17 18:27:44 +01:00
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)
2021-09-24 04:36:32 +02:00
def create_invoice(self):
invoice = super(Purchase, self).create_invoice()
if not invoice:
return
invoice.reference = self.reference
invoice.description = self.description
2023-12-13 23:37:43 +01:00
if self.invoice_method == 'shipment' and self.moves:
for move in self.moves:
invoice.reference = move.shipment.reference
break
2021-09-24 04:36:32 +02:00
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)
2021-09-10 17:03:43 +02:00
@classmethod
2021-09-10 17:16:09 +02:00
def quote(cls, purchases):
2022-10-31 16:57:01 +01:00
cls.store_cache(purchases)
2021-09-10 17:03:43 +02:00
pool = Pool()
Configuration = pool.get('purchase.configuration')
config = Configuration(1)
if config.reference_required:
cls.check_duplicated(purchases)
2021-09-10 17:16:09 +02:00
super(Purchase, cls).quote(purchases)
2021-09-10 17:03:43 +02:00
@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),
2021-09-10 17:16:09 +02:00
('purchase_date', '>=', target_date),
2021-09-10 17:03:43 +02:00
], fields_names=['reference'])
if len(duplicates) >= 2:
raise UserError('Al parecer esta compra esta duplicada!')
2022-03-24 17:31:04 +01:00
def _get_invoice_purchase(self):
'Return invoice'
invoice = super(Purchase, self)._get_invoice_purchase()
2022-03-31 22:03:46 +02:00
if self.reference and hasattr(invoice, 'reference') and not getattr(invoice, 'reference'):
invoice.reference = self.reference
2022-03-31 22:12:12 +02:00
return invoice
2022-03-24 17:31:04 +01:00
@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')
2023-08-19 19:13:55 +02:00
# 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
2021-12-09 20:43:12 +01:00
@fields.depends('product', 'purchase',
'_parent_purchase.warehouse')
def on_change_with_stock_quantity(self, name=None):
if self.product:
2021-11-23 15:55:05 +01:00
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]
2022-01-13 18:38:00 +01:00
elif self.purchase.warehouse:
location_ids = [self.purchase.warehouse.storage_location.id]
2022-01-13 18:38:00 +01:00
else:
return 0
2021-12-09 20:43:12 +01:00
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
2022-10-31 16:57:01 +01:00
@fields.depends('description')
def on_change_product(self):
2022-10-31 21:13:29 +01:00
super(Line, self).on_change_product()
2022-10-31 16:57:01 +01:00
if self.product:
self.description = self.product.name
else:
self.description = None
2022-11-03 20:38:50 +01:00
2022-02-21 15:29:20 +01:00
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)
2022-11-03 20:38:50 +01:00
edit_field = {}
if hasattr(purchase, 'approval_date'):
edit_field = {
2022-12-12 20:18:54 +01:00
'confirmation_date': None
}
if hasattr(purchase, 'confirmation_date'):
edit_field = {
2022-11-03 20:38:50 +01:00
'confirmation_date': None
}
Purchase.write([purchase], {'state': 'draft', **edit_field})
2022-02-21 15:29:20 +01:00
return 'end'
class PurchaseAnalyticStart(ModelView):
'Purchase Analytic Report Start'
2022-01-13 23:59:39 +01:00
__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'
2022-01-13 23:59:39 +01:00
__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),
])
2022-01-13 23:59:39 +01:00
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):
2022-01-13 23:59:39 +01:00
__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'],
2022-07-07 21:23:33 +02:00
'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')
2022-11-03 20:38:50 +01:00
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
2022-01-13 00:19:46 +01:00
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)
2022-01-13 00:19:46 +01:00
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')
2022-01-13 00:19:46 +01:00
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),
])
2022-01-13 00:19:46 +01:00
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'
2022-01-13 00:19:46 +01:00
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')
2022-01-14 00:59:15 +01:00
Purchase = pool.get('purchase.purchase')
2022-09-10 19:30:20 +02:00
PurchaseLine = pool.get('purchase.line')
InvoiceLine = pool.get('account.invoice.line')
2022-01-14 00:59:15 +01:00
Company = pool.get('company.company')
fields_names = [
'number', 'description', 'party.name', 'untaxed_amount',
'party.id_number', 'state', 'tax_amount', 'total_amount',
2022-09-10 19:30:20 +02:00
'currency.code'
]
fields_names_detailed = [
2022-10-14 22:13:41 +02:00
'product.rec_name', 'product.type', 'product.categories.name', 'quantity', 'unit_price',
]
2022-01-14 00:59:15 +01:00
fields = Purchase.fields_get(fields_names=['operation_center'])
2022-01-13 23:59:39 +01:00
if data['invoiced']:
title = 'INFORME DE COMPRAS'
2022-09-10 19:30:20 +02:00
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:
2022-11-03 20:38:50 +01:00
fields_add = ['invoice_date', 'purchases.description', 'purchases.number']
2022-09-10 19:30:20 +02:00
fields_names.extend(fields_add)
2022-10-14 22:13:41 +02:00
2022-09-10 19:30:20 +02:00
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'
2022-09-10 19:30:20 +02:00
if 'operation_center' in fields.keys():
fields_names_detailed.append('purchase.operation_center.name')
fields_names.append('operation_center.name')
2022-10-14 22:13:41 +02:00
2022-09-10 19:30:20 +02:00
if data['detailed']:
2022-11-03 20:38:50 +01:00
fields_add = [
'purchase.party.name', 'purchase.party.id_number',
'purchase.number', 'purchase.purchase_date']
2022-09-10 19:30:20 +02:00
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')
])
2022-10-14 22:13:41 +02:00
states = {
2022-09-10 19:30:20 +02:00
'posted': 'contabilizado',
2022-10-14 22:13:41 +02:00
'paid': 'pagada',
'draft': 'borrador',
2022-09-10 19:30:20 +02:00
'done': 'finalizado',
'processing': 'procesada',
'quotation': 'cotizacion',
}
report_context['records'] = records
2022-09-10 19:30:20 +02:00
report_context['types'] = {'goods': 'bienes', 'service': 'servicios', 'assets': 'activos'}
report_context['states'] = states
2022-01-14 00:59:15 +01:00
report_context['company'] = Company(data['company'])
report_context['data'] = data
report_context['title'] = title
return report_context
2022-10-31 16:57:01 +01:00
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', [
2022-10-31 16:57:01 +01:00
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'