# 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'