From 6cfb63fb3d124cc2ddcf1dbe79fca8f9293f46cb Mon Sep 17 00:00:00 2001 From: Sergio Morillo Date: Mon, 16 May 2022 12:49:46 +0200 Subject: [PATCH] Added specific purchase apply method. This commit refs #22792 --- __init__.py | 8 +- locale/es.po | 8 ++ purchase.py | 13 ++ sale_cost.py | 141 ++++++++++++++------ setup.py | 21 ++- tests/scenario_sale_cost_apply_purchase.rst | 34 ++++- tryton.cfg | 3 +- view/cost_form.xml | 5 + 8 files changed, 186 insertions(+), 47 deletions(-) create mode 100644 purchase.py diff --git a/__init__.py b/__init__.py index 22b1b7d..f862838 100644 --- a/__init__.py +++ b/__init__.py @@ -1,11 +1,13 @@ # The COPYRIGHT file at the top level of this repository contains # the full copyright notices and license terms. from trytond.pool import Pool -from .sale_cost import SaleCost +from . import purchase +from . import sale_cost def register(): Pool.register( - SaleCost, + sale_cost.SaleCost, + sale_cost.CostType, + purchase.Line, module='sale_cost_apply_purchase', type_='model') - diff --git a/locale/es.po b/locale/es.po index 02afa11..40d86a0 100644 --- a/locale/es.po +++ b/locale/es.po @@ -5,3 +5,11 @@ msgstr "Content-Type: text/plain; charset=utf-8\n" msgctxt "field:sale.cost,purchase:" msgid "Purchase" msgstr "Compra" + +msgctxt "field:sale.cost,purchase_lines:" +msgid "Purchase lines" +msgstr "LĂ­neas de compra" + +msgctxt "selection:sale.cost.type,apply_method:" +msgid "Purchase" +msgstr "Compra" \ No newline at end of file diff --git a/purchase.py b/purchase.py new file mode 100644 index 0000000..93332e6 --- /dev/null +++ b/purchase.py @@ -0,0 +1,13 @@ +# The COPYRIGHT file at the top level of this repository contains the full +# copyright notices and license terms. +from trytond.pool import PoolMeta + + +class Line(metaclass=PoolMeta): + __name__ = 'purchase.line' + + @classmethod + def _get_origin(cls): + result = super()._get_origin() + result.append('sale.cost') + return result diff --git a/sale_cost.py b/sale_cost.py index 3a316c8..c54c41d 100644 --- a/sale_cost.py +++ b/sale_cost.py @@ -1,16 +1,29 @@ # The COPYRIGHT file at the top level of this repository contains the full # copyright notices and license terms. +from decimal import Decimal from trytond.pool import PoolMeta, Pool from trytond.model import fields from trytond.pyson import Eval, Not, Bool, Or +from trytond.transaction import Transaction +from trytond.exceptions import UserError +from trytond.i18n import gettext +from sql.operators import Concat +from functools import partial +from itertools import groupby class SaleCost(metaclass=PoolMeta): __name__ = 'sale.cost' - purchase = fields.Many2One('purchase.purchase', 'Purchase', readonly=True, + purchase = fields.Function( + fields.Many2One('purchase.purchase', 'Purchase', readonly=True, states={ - 'invisible': Eval('apply_method', '') != 'invoice_in' + 'invisible': Eval('apply_method', '') != 'purchase' + }, depends=['apply_method']), 'get_purchase') + purchase_lines = fields.One2Many('purchase.line', 'origin', + 'Purchase lines', + states={ + 'invisible': Eval('apply_method', '') != 'purchase' }, depends=['apply_method']) @classmethod @@ -26,67 +39,117 @@ class SaleCost(metaclass=PoolMeta): invisible_condition, Not(Bool(Eval('purchase')))) cls._buttons['unapply']['depends'].append('purchase') + cls.apply_method.selection.append(('purchase', 'Purchase')) + cls.invoice_party.states['invisible'] &= ( + Eval('apply_method') != 'purchase') @classmethod - def create_invoice_lines(cls, apply_method, costs): + def __register__(cls, module_name): + table = cls.__table_handler__(module_name) + Purchase_lines = Pool().get('purchase.line') + cursor = Transaction().connection.cursor() + sql_table = cls.__table__() + purchase_line = Purchase_lines.__table__() + + purchase_exists = table.column_exist('purchase') + + super(SaleCost, cls).__register__(module_name) + + if purchase_exists: + cursor.execute(*purchase_line.join(sql_table, + condition=purchase_line.purchase == sql_table.purchase + ).select( + purchase_line.id, + sql_table.id, + where=(purchase_line.type == 'line')) + ) + + for pline_id, cost_id in cursor.fetchall(): + cursor.execute(*purchase_line.update( + columns=[purchase_line.origin], + values=[Concat(cls.__name__ + ',', cost_id)], + where=purchase_line.id == pline_id + )) + + table.drop_column('purchase') + + def get_purchase(self, name=None): + if self.purchase_lines: + return self.purchase_lines[0].purchase.id + + @classmethod + def _apply_method(cls, apply_method, costs): Purchase = Pool().get('purchase.purchase') - if apply_method != 'invoice_in': - return super().create_invoice_lines(apply_method, costs) + if apply_method != 'purchase': + return super()._apply_method(apply_method, costs) - lines = [] - for cost in costs: - lines.extend(cost._get_purchase_lines(apply_method)) - if lines: - Purchase.quote(list(set([l.purchase for l in lines]))) + cost_keyfunc = partial(cls._get_purchase_keygroup) + sorted_cost_lines = sorted(costs, key=cost_keyfunc) + purchases = [] + for key, grouped_lines in groupby(sorted_cost_lines, key=cost_keyfunc): + purchase = Purchase(**dict(key)) + purchase.on_change_party() + lines = [] + + group_lines = list(grouped_lines) + for cost in group_lines: + lines.append(cost._get_purchase_line(purchase)) + purchase.lines = lines + purchases.append(purchase) + Purchase.save(purchases) + Purchase.quote(purchases) return [] - def _get_purchase_lines(self, apply_method): + def _get_purchase_line(self, purchase): pool = Pool() PurchaseLine = pool.get('purchase.line') - Purchase = pool.get('purchase.purchase') - - if not self.amount or self.purchase: - return [] - purchase = Purchase( - party=self.invoice_party, - company=self.document.company, - purchase_date=self.sale.sale_date) - purchase.on_change_party() - self.purchase = purchase line = PurchaseLine() + line.purchase = purchase + line.type = 'line' line.product = self.type_.product - line.quantity = 1 - line.unit = self.type_.product.default_uom line.on_change_product() + line.quantity = self.quantity line.on_change_quantity() - line.unit_price = self.amount - taxes = [] - for tax in line.product.supplier_taxes_used: - if self.invoice_party.supplier_tax_rule: - pattern = self._get_tax_rule_pattern() - tax_ids = self.invoice_party.supplier_tax_rule.apply( - tax, pattern) - if tax_ids: - taxes.extend(tax_ids) - continue - taxes.append(tax.id) - line.taxes = taxes - purchase.lines = [line] - self.save() - return list(purchase.lines) + digits = PurchaseLine.unit_price.digits[1] + line.unit_price = (self.amount / Decimal(str(self.quantity)) + ).quantize(Decimal(10) ** -Decimal(digits)) + line.amount = line.on_change_with_amount() + line.origin = self + return line + + @classmethod + def _get_purchase_keygroup(cls, cost): + if not cost.invoice_party: + raise UserError(gettext('document_cost_apply_invoice.' + 'msg_document_cost_apply_invoice_invoice_no_party', + type_=cost.type_.rec_name, + cost=cost.document.rec_name)) + return [ + ('company', cost.document.company), + ('party', cost.invoice_party), + ('purchase_date', cost.sale.sale_date)] @classmethod def _unapply_method(cls, apply_method, costs): pool = Pool() Purchase = pool.get('purchase.purchase') - if apply_method != 'invoice_in': + if apply_method != 'purchase': super(SaleCost, cls)._unapply_method(apply_method, costs) purchases = Purchase.search([ ('id', 'in', [c.purchase.id for c in costs if c.purchase])]) if purchases: Purchase.delete(purchases) + + +class CostType(metaclass=PoolMeta): + __name__ = 'sale.cost.type' + + @classmethod + def __setup__(cls): + super(CostType, cls).__setup__() + cls.apply_method.selection.append(('purchase', 'Purchase')) diff --git a/setup.py b/setup.py index 1415e5c..78baf40 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,8 @@ import io from configparser import ConfigParser MODULE2PREFIX = { - 'sale_cost_apply_invoice': 'datalife' + 'sale_cost_apply_invoice': 'datalife', + 'purchase_origin': 'datalife' } @@ -53,6 +54,19 @@ dependency_links = { 'trytond-sale_cost_apply_invoice@%(branch)s' '#egg=datalife_sale_cost_apply_invoice' % { 'branch': branch, + }, + 'purchase_origin': + 'git+https://gitlab.com/datalifeit/' + 'trytond-purchase_origin@%(branch)s' + '#egg=datalife_purchase_origin' % { + 'branch': branch, + }, + 'sale_processing2confirmed': + 'git+https://gitlab.com/datalifeit/' + 'trytond-sale_processing2confirmed@%(branch)s' + '#egg=nantic_sale_processing2confirmed-%(series)s' % { + 'branch': branch, + 'series': series } } @@ -69,7 +83,10 @@ for dep in info.get('depends', []): requires.append(get_require_version('trytond')) -tests_require = [get_require_version('proteus')] +tests_require = [ + get_require_version('proteus'), + get_require_version('nantic_sale_processing2confirmed'), +] dependency_links = list(dependency_links.values()) if minor_version % 2: diff --git a/tests/scenario_sale_cost_apply_purchase.rst b/tests/scenario_sale_cost_apply_purchase.rst index 5d796b9..21d3325 100644 --- a/tests/scenario_sale_cost_apply_purchase.rst +++ b/tests/scenario_sale_cost_apply_purchase.rst @@ -20,7 +20,7 @@ Imports:: Install sale_cost_apply_purchase:: - >>> config = activate_modules('sale_cost_apply_purchase') + >>> config = activate_modules(['sale_cost_apply_purchase', 'sale_processing2confirmed']) Create company:: @@ -165,6 +165,36 @@ Sale 2 products:: >>> sale.untaxed_amount, sale.tax_amount, sale.total_amount (Decimal('29.00'), Decimal('2.90'), Decimal('31.90')) +Create cost type purchase:: + + >>> type_invoice.apply_method = 'purchase' + >>> type_invoice.product = service + >>> type_invoice.save() + +Check purchase lines:: + + >>> SaleCost = Model.get('sale.cost') + >>> sale_cost = SaleCost() + >>> sale_cost.invoice_party = supplier + >>> sale_cost.document = sale + >>> sale_cost.type_ = type_invoice + >>> sale_cost.template = template + >>> sale_cost.save() + >>> sale_cost.click('apply') + >>> len([sale_cost.purchase]) + 1 + >>> PurchaseLine = Model.get('purchase.line') + >>> line, = PurchaseLine.find([()]) + >>> line.origin == sale_cost + True + >>> sale_cost.click('unapply') + >>> sale_cost.reload() + >>> lines = PurchaseLine.find([()]) + >>> len(lines) + 0 + >>> sale_cost.click('delete') + + Check cost applying:: >>> invoice_cost, = sale.costs @@ -180,7 +210,7 @@ Check cost applying:: True >>> line.unit_price == invoice_cost.amount True - >>> sale.click('quote') + >>> sale.click('unprocess') >>> lines = PurchaseLine.find([()]) >>> len(lines) 0 \ No newline at end of file diff --git a/tryton.cfg b/tryton.cfg index 080d9f8..d5ecb2b 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -5,5 +5,6 @@ depends: res sale_cost_apply_invoice purchase + purchase_origin xml: - sale_cost.xml + sale_cost.xml \ No newline at end of file diff --git a/view/cost_form.xml b/view/cost_form.xml index 7db8922..e13af20 100644 --- a/view/cost_form.xml +++ b/view/cost_form.xml @@ -6,4 +6,9 @@