コード例 #1
0
class AssetRevision(ModelSQL, ModelView):
    "Asset Revision"
    __name__ = 'account.asset.revision'
    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')
    value = Monetary("Asset Value",
                     currency='currency',
                     digits='currency',
                     required=True)
    residual_value = Monetary("Residual Value",
                              currency='currency',
                              digits='currency',
                              required=True)
    end_date = fields.Date("End Date", required=True)
    origin = fields.Reference("Origin", selection='get_origins', select=True)
    description = fields.Char("Description")
    asset = fields.Many2One('account.asset',
                            "Asset",
                            select=True,
                            required=True)

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('asset')

    @fields.depends('asset', '_parent_asset.currency')
    def on_change_with_currency(self, name=None):
        if self.asset and self.asset.currency:
            return self.asset.currency.id

    @fields.depends('origin', 'value', 'asset', '_parent_asset.value')
    def on_change_origin(self, name=None):
        pool = Pool()
        InvoiceLine = pool.get('account.invoice.line')
        if isinstance(self.origin, InvoiceLine) and self.origin.id >= 0:
            self.value = self.asset.value + self.origin.amount

    @staticmethod
    def _get_origin():
        "Return list of Model names for origin Reference"
        return ['account.invoice.line']

    @classmethod
    def get_origins(cls):
        pool = Pool()
        IrModel = pool.get('ir.model')

        get_name = IrModel.get_name
        models = cls._get_origin()
        return [(None, '')] + [(m, get_name(m)) for m in models]
コード例 #2
0
class TestPaymentTermView(ModelView):
    'Test Payment Term'
    __name__ = 'account.invoice.payment_term.test'
    payment_term = fields.Many2One('account.invoice.payment_term',
        'Payment Term', required=True)
    date = fields.Date('Date')
    amount = Monetary(
        "Amount", currency='currency', digits='currency', required=True)
    currency = fields.Many2One('currency.currency', 'Currency', required=True)
    result = fields.One2Many('account.invoice.payment_term.test.result',
        None, 'Result', readonly=True)

    @staticmethod
    def default_currency():
        pool = Pool()
        Company = pool.get('company.company')
        company = Transaction().context.get('company')
        if company:
            return Company(company).currency.id

    @fields.depends('payment_term', 'date', 'amount', 'currency', 'result')
    def on_change_with_result(self):
        pool = Pool()
        Result = pool.get('account.invoice.payment_term.test.result')
        result = []
        if (self.payment_term and self.amount and self.currency):
            for date, amount in self.payment_term.compute(
                    self.amount, self.currency, self.date):
                result.append(Result(
                        date=date,
                        amount=amount,
                        currency=self.currency))
        self.result = result
        return self._changed_values.get('result', [])
コード例 #3
0
class TestPaymentTermViewResult(ModelView):
    'Test Payment Term'
    __name__ = 'account.invoice.payment_term.test.result'
    date = fields.Date('Date', readonly=True)
    amount = Monetary(
        "Amount", currency='currency', digits='currency', readonly=True)
    currency = fields.Many2One('currency.currency', "Currency")
コード例 #4
0
class WeightPriceList(ModelSQL, ModelView):
    'Carrier Weight Price List'
    __name__ = 'carrier.weight_price_list'
    carrier = fields.Many2One(
        'carrier',
        'Carrier',
        required=True,
        select=True,
        help="The carrier that the price list belongs to.")
    weight = fields.Float('Weight',
                          digits=(16, Eval('_parent_carrier',
                                           {}).get('weight_uom_digits', 2)),
                          depends={'carrier'},
                          help="The lower limit for the price.")
    price = Monetary("Price",
                     currency='currency',
                     digits='currency',
                     help="The price of the carrier service.")

    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')

    @classmethod
    def __setup__(cls):
        super(WeightPriceList, cls).__setup__()
        cls._order.insert(0, ('weight', 'ASC'))

    @fields.depends('carrier', '_parent_carrier.weight_currency')
    def on_change_with_currency(self, name=None):
        if self.carrier and self.carrier.weight_currency:
            return self.carrier.weight_currency.id
コード例 #5
0
class AssetLine(ModelSQL, ModelView):
    'Asset Line'
    __name__ = 'account.asset.line'
    asset = fields.Many2One('account.asset',
                            'Asset',
                            required=True,
                            ondelete='CASCADE',
                            readonly=True)
    date = fields.Date('Date', readonly=True)
    depreciation = Monetary("Depreciation",
                            currency='currency',
                            digits='currency',
                            required=True,
                            readonly=True)
    acquired_value = Monetary("Acquired Value",
                              currency='currency',
                              digits='currency',
                              readonly=True)
    depreciable_basis = Monetary("Depreciable Basis",
                                 currency='currency',
                                 digits='currency',
                                 readonly=True)
    actual_value = Monetary("Actual Value",
                            currency='currency',
                            digits='currency',
                            readonly=True)
    accumulated_depreciation = Monetary("Accumulated Depreciation",
                                        currency='currency',
                                        digits='currency',
                                        readonly=True)
    move = fields.Many2One('account.move', 'Account Move', readonly=True)
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'),
        'on_change_with_currency')

    @classmethod
    def __setup__(cls):
        super(AssetLine, cls).__setup__()
        cls.__access__.add('asset')
        cls._order.insert(0, ('date', 'ASC'))

    @fields.depends('asset', '_parent_asset.currency')
    def on_change_with_currency(self, name=None):
        if self.asset:
            return self.asset.currency.id
コード例 #6
0
class PurchaseRequisitionLine(sequence_ordered(), ModelSQL, ModelView):
    "Purchase Requisition Line"
    __name__ = 'purchase.requisition.line'
    _states = {
        'readonly': Eval('purchase_requisition_state') != 'draft',
        }

    requisition = fields.Many2One(
        'purchase.requisition', 'Requisition',
        ondelete='CASCADE', select=True, required=True)
    supplier = fields.Many2One('party.party', 'Supplier', states=_states)
    product = fields.Many2One(
        'product.product', 'Product',
        ondelete='RESTRICT',
        domain=[
            ('purchasable', '=', True),
            ],
        states=_states)
    product_uom_category = fields.Function(
        fields.Many2One('product.uom.category', "Product UOM Category"),
        'on_change_with_product_uom_category')
    description = fields.Text("Description", states=_states)
    summary = fields.Function(fields.Char('Summary'), 'on_change_with_summary')
    quantity = fields.Float(
        "Quantity", digits='unit', required=True, states=_states)
    unit = fields.Many2One(
        'product.uom', 'Unit', ondelete='RESTRICT',
        states={
            'required': Bool(Eval('product')),
            'readonly': _states['readonly'],
            })
    unit_price = Monetary(
        'Unit Price', currency='currency', digits=price_digits, states=_states)
    currency = fields.Function(fields.Many2One('currency.currency',
        'Currency'), 'on_change_with_currency')
    amount = fields.Function(Monetary(
            "Amount", currency='currency', digits='currency'),
        'on_change_with_amount')
    purchase_requests = fields.One2Many(
        'purchase.request', 'origin', 'Purchase Request', readonly=True)
    purchase_requisition_state = fields.Function(fields.Selection(
            'get_purchase_requisition_states', "Purchase Requisition State"),
        'on_change_with_purchase_requisition_state')

    del _states

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('requisition')
        unit_categories = cls._unit_categories()
        cls.unit.domain = [
            If(Bool(Eval('product_uom_category')),
                ('category', 'in', [Eval(c) for c in unit_categories]),
                ('category', '!=', -1)),
            ]

    @classmethod
    def _unit_categories(cls):
        return ['product_uom_category']

    @fields.depends('product')
    def on_change_with_product_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom_category.id

    @fields.depends('requisition', '_parent_requisition.currency')
    def on_change_with_currency(self, name=None):
        if self.requisition and self.requisition.currency:
            return self.requisition.currency.id

    @classmethod
    def get_purchase_requisition_states(cls):
        pool = Pool()
        Requisition = pool.get('purchase.requisition')
        return Requisition.fields_get(['state'])['state']['selection']

    @fields.depends('requisition', '_parent_requisition.state')
    def on_change_with_purchase_requisition_state(self, name=None):
        if self.requisition:
            return self.requisition.state

    @fields.depends('product', 'unit', 'quantity', 'supplier')
    def on_change_product(self):
        if not self.product:
            return

        category = self.product.purchase_uom.category
        if not self.unit or self.unit.category != category:
            self.unit = self.product.purchase_uom

    @fields.depends('description')
    def on_change_with_summary(self, name=None):
        return firstline(self.description or '')

    @fields.depends('quantity', 'unit_price', 'unit', 'requisition',
        '_parent_requisition.currency')
    def on_change_with_amount(self, name=None):
        if (self.unit_price is None) or (self.quantity is None):
            return None
        amount = Decimal(str(self.quantity)) * self.unit_price
        if self.requisition.currency:
            amount = self.requisition.currency.round(amount)
        return amount

    def get_rec_name(self, name):
        pool = Pool()
        Lang = pool.get('ir.lang')
        if self.product:
            lang = Lang.get()
            return (lang.format_number_symbol(
                    self.quantity or 0, self.unit, digits=self.unit.digits)
                + ' %s @ %s' % (
                    self.product.rec_name, self.requisition.rec_name))
        else:
            return self.requisition.rec_name

    def _get_purchase_request_product_supplier_pattern(self):
        pattern = {
            'company': self.requisition.company.id,
            }
        if self.supplier:
            pattern['party'] = self.supplier.id
        return pattern

    @property
    def request_unit(self):
        unit = self.unit
        if (self.product
                and self.product.purchase_uom.category == self.unit.category):
            unit = self.product.purchase_uom
        return unit

    @property
    def request_quantity(self):
        pool = Pool()
        Uom = pool.get('product.uom')
        quantity = self.quantity
        request_unit = self.request_unit
        if (self.product
                and request_unit
                and request_unit.category == self.unit.category):
            quantity = Uom.compute_qty(
                self.unit, self.quantity, request_unit, round=True)
        return quantity

    @property
    def request_unit_price(self):
        return self.unit_price

    def compute_request(self):
        """
        Return the value of the purchase request which will answer to
        the needed quantity at the given date.
        """
        pool = Pool()
        Uom = pool.get('product.uom')
        Request = pool.get('purchase.request')

        if self.purchase_requests:
            return

        supply_date = self.requisition.supply_date
        supplier = None
        purchase_date = None

        if self.product:
            supplier, purchase_date = Request.find_best_supplier(
                self.product, supply_date,
                **self._get_purchase_request_product_supplier_pattern())
        elif self.supplier:
            lead_time = self.supplier.get_multivalue(
                'supplier_lead_time', company=self.requisition.company.id)
            if lead_time is not None:
                purchase_date = supply_date - lead_time

        uom = self.request_unit
        quantity = self.request_quantity
        if (self.product
                and self.product.purchase_uom.category == self.unit.category):
            uom = self.product.purchase_uom
            quantity = Uom.compute_qty(self.unit, self.quantity,
                uom, round=True)

        return Request(
            product=self.product,
            description=self.description,
            party=supplier or self.supplier,
            quantity=quantity,
            uom=uom,
            computed_quantity=self.quantity,
            computed_uom=self.unit,
            purchase_date=purchase_date,
            supply_date=supply_date,
            company=self.requisition.company,
            warehouse=self.requisition.warehouse,
            origin=self,
            )

    @classmethod
    def copy(cls, lines, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('purchase_requests')
        return super(PurchaseRequisitionLine, cls).copy(lines, default=default)
コード例 #7
0
class PaymentTermLine(sequence_ordered(), ModelSQL, ModelView):
    'Payment Term Line'
    __name__ = 'account.invoice.payment_term.line'
    payment = fields.Many2One('account.invoice.payment_term', 'Payment Term',
            required=True, ondelete="CASCADE")
    type = fields.Selection([
            ('fixed', 'Fixed'),
            ('percent', 'Percentage on Remainder'),
            ('percent_on_total', 'Percentage on Total'),
            ('remainder', 'Remainder'),
            ], 'Type', required=True)
    ratio = fields.Numeric('Ratio', digits=(14, 10),
        states={
            'invisible': ~Eval('type').in_(['percent', 'percent_on_total']),
            'required': Eval('type').in_(['percent', 'percent_on_total']),
            })
    divisor = fields.Numeric('Divisor', digits=(10, 14),
        states={
            'invisible': ~Eval('type').in_(['percent', 'percent_on_total']),
            'required': Eval('type').in_(['percent', 'percent_on_total']),
            })
    amount = Monetary(
        "Amount", currency='currency', digits='currency',
        states={
            'invisible': Eval('type') != 'fixed',
            'required': Eval('type') == 'fixed',
            })
    currency = fields.Many2One('currency.currency', 'Currency',
        states={
            'invisible': Eval('type') != 'fixed',
            'required': Eval('type') == 'fixed',
            })
    relativedeltas = fields.One2Many(
        'account.invoice.payment_term.line.delta', 'line', 'Deltas')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('payment')

    @classmethod
    def __register__(cls, module_name):
        sql_table = cls.__table__()
        super(PaymentTermLine, cls).__register__(module_name)
        cursor = Transaction().connection.cursor()
        table = cls.__table_handler__(module_name)

        # Migration from 3.8: rename percentage into ratio
        if table.column_exist('percentage'):
            cursor.execute(*sql_table.update(
                    columns=[sql_table.ratio],
                    values=[sql_table.percentage / 100]))
            table.drop_column('percentage')

    @staticmethod
    def default_type():
        return 'remainder'

    @classmethod
    def default_relativedeltas(cls):
        if Transaction().user == 0:
            return []
        return [{}]

    @fields.depends('type')
    def on_change_type(self):
        if self.type != 'fixed':
            self.amount = Decimal('0.0')
            self.currency = None
        if self.type not in ('percent', 'percent_on_total'):
            self.ratio = Decimal('0.0')
            self.divisor = Decimal('0.0')

    @fields.depends('ratio')
    def on_change_ratio(self):
        if not self.ratio:
            self.divisor = Decimal('0.0')
        else:
            self.divisor = self.round(1 / self.ratio,
                self.__class__.divisor.digits[1])

    @fields.depends('divisor')
    def on_change_divisor(self):
        if not self.divisor:
            self.ratio = Decimal('0.0')
        else:
            self.ratio = self.round(1 / self.divisor,
                self.__class__.ratio.digits[1])

    def get_date(self, date):
        for relativedelta_ in self.relativedeltas:
            date += relativedelta_.get()
        return date

    def get_value(self, remainder, amount, currency):
        Currency = Pool().get('currency.currency')
        if self.type == 'fixed':
            fixed = Currency.compute(self.currency, self.amount, currency)
            return fixed.copy_sign(amount)
        elif self.type == 'percent':
            return currency.round(remainder * self.ratio)
        elif self.type == 'percent_on_total':
            return currency.round(amount * self.ratio)
        elif self.type == 'remainder':
            return currency.round(remainder)
        return None

    @staticmethod
    def round(number, digits):
        quantize = Decimal(10) ** -Decimal(digits)
        return Decimal(number).quantize(quantize)

    @classmethod
    def validate_fields(cls, lines, field_names):
        super().validate_fields(lines, field_names)
        cls.check_ratio_and_divisor(lines, field_names)

    @classmethod
    def check_ratio_and_divisor(cls, lines, field_names=None):
        "Check consistency between ratio and divisor"
        if field_names and not (field_names & {'type', 'ratio', 'divisor'}):
            return
        for line in lines:
            if line.type not in ('percent', 'percent_on_total'):
                continue
            if line.ratio is None or line.divisor is None:
                raise PaymentTermValidationError(
                    gettext('account_invoice'
                        '.msg_payment_term_invalid_ratio_divisor',
                        line=line.rec_name))
            if (line.ratio != round(
                        1 / line.divisor, cls.ratio.digits[1])
                    and line.divisor != round(
                        1 / line.ratio, cls.divisor.digits[1])):
                raise PaymentTermValidationError(
                    gettext('account_invoice'
                        '.msg_payment_term_invalid_ratio_divisor',
                        line=line.rec_name))
コード例 #8
0
class _Action_Line:
    __slots__ = ()
    _states = {
        'readonly': ((Eval('complaint_state') != 'draft')
                     | Bool(Eval('_parent_action.result', True))),
    }

    action = fields.Many2One('sale.complaint.action',
                             'Action',
                             ondelete='CASCADE',
                             select=True,
                             required=True)
    quantity = fields.Float("Quantity", digits='unit', states=_states)
    unit = fields.Function(fields.Many2One('product.uom', "Unit"),
                           'on_change_with_unit')
    unit_price = Monetary("Unit Price",
                          currency='currency',
                          digits=price_digits,
                          states=_states,
                          help='Leave empty for the same price.')

    amount = fields.Function(
        Monetary("Amount", currency='currency', digits='currency'),
        'on_change_with_amount')
    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')

    complaint_state = fields.Function(
        fields.Selection('get_complaint_states', "Complaint State"),
        'on_change_with_complaint_state')
    complaint_origin_id = fields.Function(
        fields.Integer("Complaint Origin ID"),
        'on_change_with_complaint_origin_id')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('action')

    def on_change_with_unit(self, name=None):
        raise NotImplementedError

    @fields.depends('currency', methods=['get_quantity', 'get_unit_price'])
    def on_change_with_amount(self, name=None):
        quantity = self.get_quantity() or 0
        unit_price = self.get_unit_price() or Decimal(0)
        amount = Decimal(str(quantity)) * unit_price
        if self.currency:
            amount = self.currency.round(amount)
        return amount

    def get_quantity(self):
        raise NotImplementedError

    def get_unit_price(self):
        raise NotImplementedError

    @fields.depends('action', '_parent_action.currency')
    def on_change_with_currency(self, name=None):
        if self.action and self.action.currency:
            return self.action.currency.id

    @classmethod
    def get_complaint_states(cls):
        pool = Pool()
        Complaint = pool.get('sale.complaint')
        return Complaint.fields_get(['state'])['state']['selection']

    @fields.depends('action', '_parent_action.complaint',
                    '_parent_action._parent_complaint.state')
    def on_change_with_complaint_state(self, name=None):
        if self.action and self.action.complaint:
            return self.action.complaint.state

    @fields.depends('action', '_parent_action.complaint',
                    '_parent_action._parent_complaint.origin_id')
    def on_change_with_complaint_origin_id(self, name=None):
        if self.action and self.action.complaint:
            return self.action.complaint.origin_id
コード例 #9
0
class PurchaseRequisition(Workflow, ModelSQL, ModelView):
    "Purchase Requisition"
    __name__ = 'purchase.requisition'
    _rec_name = 'number'
    _states = {
        'readonly': Eval('state') != 'draft',
        }

    company = fields.Many2One(
        'company.company', "Company", required=True, select=True,
        states={
            'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
            })
    number = fields.Char('Number', readonly=True, select=True)
    description = fields.Char('Description', states=_states)
    employee = fields.Many2One(
        'company.employee', 'Employee', required=True, states=_states)
    supply_date = fields.Date(
        'Supply Date',
        states={
            'required': ~Eval('state').in_(['draft', 'cancelled']),
            'readonly': _states['readonly'],
            })
    warehouse = fields.Many2One(
        'stock.location', 'Warehouse',
        domain=[
            ('type', '=', 'warehouse'),
            ],
        states=_states)
    currency = fields.Many2One(
        'currency.currency', 'Currency',
        states={
            'readonly': (_states['readonly']
                | (Eval('lines', [0]) & Eval('currency'))),
            })
    total_amount = fields.Function(
        Monetary("Total", currency='currency', digits='currency'),
        'get_amount')
    total_amount_cache = Monetary(
        "Total Cache", currency='currency', digits='currency')
    lines = fields.One2Many(
        'purchase.requisition.line', 'requisition', 'Lines',
        states=_states)

    approved_by = employee_field(
        "Approved By", states=['approved', 'processing', 'done', 'cancelled'])
    rejected_by = employee_field(
        "Rejected By", states=['rejected', 'processing', 'done', 'cancelled'])
    state = fields.Selection([
            ('draft', "Draft"),
            ('waiting', "Waiting"),
            ('rejected', "Rejected"),
            ('approved', "Approved"),
            ('processing', "Processing"),
            ('done', "Done"),
            ('cancelled', "Cancelled"),
            ], "State", readonly=True, required=True, sort=False)

    del _states

    @classmethod
    def __setup__(cls):
        super(PurchaseRequisition, cls).__setup__()
        cls._transitions |= set((
                ('cancelled', 'draft'),
                ('rejected', 'draft'),
                ('draft', 'cancelled'),
                ('draft', 'waiting'),
                ('waiting', 'draft'),
                ('waiting', 'rejected'),
                ('waiting', 'approved'),
                ('approved', 'processing'),
                ('approved', 'draft'),
                ('processing', 'done'),
                ('done', 'processing'),
                ))
        cls._buttons.update({
                'cancel': {
                    'invisible': Eval('state') != 'draft',
                    'depends': ['state'],
                    },
                'draft': {
                    'invisible': ~Eval('state').in_(
                        ['cancelled', 'waiting', 'approved', 'rejected']),
                    'icon': If(Eval('state').in_(['cancelled', 'rejected']),
                        'tryton-undo',
                        'tryton-back'),
                    'depends': ['state'],
                    },
                'wait': {
                    'pre_validate': [('supply_date', '!=', None)],
                    'invisible': ((Eval('state') != 'draft')
                        | ~Eval('lines', [])),
                    'readonly': ~Eval('lines', []),
                    'depends': ['state'],
                    },
                'approve': {
                    'invisible': Eval('state') != 'waiting',
                    'depends': ['state'],
                    },
                'process': {
                    'invisible': ~Eval('state').in_(
                        ['approved', 'processing']),
                    'icon': If(Eval('state') == 'approved',
                        'tryton-forward', 'tryton-refresh'),
                    'depends': ['state'],
                    },
                'reject': {
                    'invisible': Eval('state') != 'waiting',
                    'depends': ['state'],
                    },
                })
        # The states where amounts are cached
        cls._states_cached = ['approved', 'done', 'rejected',
            'processing', 'cancelled']

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        table = cls.__table__()

        super().__register__(module_name)

        # Migration from 5.6: rename state cancel to cancelled
        cursor.execute(*table.update(
                [table.state], ['cancelled'],
                where=table.state == 'cancel'))

    @classmethod
    def default_state(cls):
        return 'draft'

    @classmethod
    def default_company(cls):
        return Transaction().context.get('company')

    @classmethod
    def default_employee(cls):
        return Transaction().context.get('employee')

    @classmethod
    def default_warehouse(cls):
        Location = Pool().get('stock.location')
        return Location.get_default_warehouse()

    @classmethod
    def default_currency(cls):
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            return Company(company).currency.id

    @fields.depends('lines', 'currency')
    def on_change_with_total_amount(self):
        self.total_amount = Decimal('0.0')
        if self.lines:
            for line in self.lines:
                self.total_amount += getattr(line, 'amount', None) or 0
        if self.currency:
            self.total_amount = self.currency.round(self.total_amount)
        return self.total_amount

    @classmethod
    def store_cache(cls, requisitions):
        for requisition in requisitions:
            requisition.total_amount_cache = requisition.total_amount
        cls.save(requisitions)

    @classmethod
    def get_amount(cls, requisitions, name):
        total_amount = {}

        # Sort cached first and re-instantiate to optimize cache management
        requisitions = sorted(requisitions,
            key=lambda r: r.state in cls._states_cached, reverse=True)
        requisitions = cls.browse(requisitions)
        for requisition in requisitions:
            if (requisition.state in cls._states_cached
                    and requisition.total_amount_cache is not None):
                total_amount[requisition.id] = requisition.total_amount_cache
            else:
                total_amount[requisition.id] = (
                    requisition.on_change_with_total_amount())
        return total_amount

    @classmethod
    def create_requests(cls, requisitions):
        pool = Pool()
        Request = pool.get('purchase.request')
        requests = []
        for requisition in requisitions:
            for line in requisition.lines:
                request = line.compute_request()
                if request:
                    requests.append(request)
        if requests:
            Request.save(requests)

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree', 'visual',
                If(Eval('state') == 'cancelled', 'muted', '')),
            ]

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        Config = pool.get('purchase.configuration')

        config = Config(1)
        vlist = [v.copy() for v in vlist]
        default_company = cls.default_company()
        for values in vlist:
            if values.get('number') is None:
                values['number'] = config.get_multivalue(
                    'purchase_requisition_sequence',
                    company=values.get('company', default_company)).get()
        return super(PurchaseRequisition, cls).create(vlist)

    @classmethod
    def delete(cls, requisitions):
        # Cancel before delete
        cls.cancel(requisitions)
        for requisition in requisitions:
            if requisition.state != 'cancelled':
                raise AccessError(
                    gettext('purchase_requisition.msg_delete_cancel',
                        requisition=requisition.rec_name))
        super(PurchaseRequisition, cls).delete(requisitions)

    def check_for_waiting(self):
        if not self.warehouse:
            for line in self.lines:
                if line.product and line.product.type in {'goods', 'assets'}:
                    raise RequiredValidationError(
                        gettext('purchase_requisition.msg_warehouse_required',
                            requisition=self.rec_name))

    @classmethod
    def copy(cls, requisitions, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('number', None)
        default.setdefault('supply_date', None)
        default.setdefault('approved_by')
        default.setdefault('rejected_by')
        return super(PurchaseRequisition, cls).copy(
            requisitions, default=default)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, requisitions):
        cls.store_cache(requisitions)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    @reset_employee('approved_by', 'rejected_by')
    def draft(cls, requisitions):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('waiting')
    def wait(cls, requisitions):
        for requisition in requisitions:
            requisition.check_for_waiting()

    @classmethod
    @ModelView.button
    @Workflow.transition('rejected')
    @set_employee('rejected_by')
    def reject(cls, requisitions):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('approved')
    @set_employee('approved_by')
    def approve(cls, requisitions):
        pool = Pool()
        Configuration = pool.get('purchase.configuration')
        transaction = Transaction()
        context = transaction.context
        cls.store_cache(requisitions)
        config = Configuration(1)
        with transaction.set_context(
                queue_scheduled_at=config.purchase_process_after,
                queue_batch=context.get('queue_batch', True)):
            cls.__queue__.process(requisitions)

    @classmethod
    @Workflow.transition('processing')
    def proceed(cls, requisitions):
        pass

    @classmethod
    @Workflow.transition('done')
    def do(cls, requisitions):
        pass

    @classmethod
    @ModelView.button
    def process(cls, requisitions):
        done = []
        process = []
        requisitions = [r for r in requisitions
            if r.state in {'approved', 'processing', 'done'}]
        cls.create_requests(requisitions)
        for requisition in requisitions:
            if requisition.is_done():
                if requisition.state != 'done':
                    done.append(requisition)
            elif requisition.state != 'processing':
                process.append(requisition)
        if process:
            cls.proceed(process)
        if done:
            cls.do(done)

    def is_done(self):
        return all(
            r.purchase and r.purchase.state in {'cancelled', 'confirmed'}
            for l in self.lines for r in l.purchase_requests)
コード例 #10
0
class Work(metaclass=PoolMeta):
    __name__ = 'project.work'
    product = fields.Many2One('product.product',
                              'Product',
                              domain=[
                                  ('type', '=', 'service'),
                              ],
                              context={
                                  'company': Eval('company', -1),
                              },
                              depends={'company'})
    list_price = Monetary("List Price",
                          currency='currency',
                          digits=price_digits)
    revenue = fields.Function(
        Monetary("Revenue", currency='currency', digits='currency'),
        'get_total')
    cost = fields.Function(
        Monetary("Cost", currency='currency', digits='currency'), 'get_total')
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'),
        'on_change_with_currency')

    @classmethod
    def __setup__(cls):
        pool = Pool()

        super(Work, cls).__setup__()

        try:
            pool.get('purchase.line')
        except KeyError:
            pass
        else:
            # Add purchase lines if purchase is activated
            cls.purchase_lines = fields.One2Many('purchase.line',
                                                 'work',
                                                 'Purchase Lines',
                                                 domain=[
                                                     ('purchase.company', '=',
                                                      Eval('company', -1)),
                                                     ('type', '=', 'line'),
                                                 ])

    @classmethod
    def _get_cost(cls, works):
        pool = Pool()
        Line = pool.get('timesheet.line')
        Work = pool.get('timesheet.work')
        transaction = Transaction()
        cursor = transaction.connection.cursor()

        costs = dict.fromkeys([w.id for w in works], 0)

        table = cls.__table__()
        work = Work.__table__()
        line = Line.__table__()

        # Timesheet cost
        work_ids = [w.id for w in works]
        for sub_ids in grouped_slice(work_ids):
            red_sql = reduce_ids(table.id, sub_ids)
            cursor.execute(
                *table.join(work,
                            condition=(Concat(cls.__name__ +
                                              ',', table.id) == work.origin)).
                join(line, condition=line.work == work.id).select(
                    table.id,
                    Sum(line.cost_price * line.duration),
                    where=red_sql,
                    group_by=[table.id]))
            for work_id, cost in cursor:
                # SQLite stores timedelta as float
                if not isinstance(cost, float):
                    cost = cost.total_seconds()
                # Convert from seconds
                cost /= 60 * 60
                costs[work_id] += Decimal(str(cost))

        # Purchase cost
        if hasattr(cls, 'purchase_lines'):
            for work_id, cost in cls._purchase_cost(works).items():
                costs[work_id] += cost

        for work in works:
            costs[work.id] = work.company.currency.round(costs[work.id])
        return costs

    @classmethod
    def _purchase_cost(cls, works):
        'Compute direct purchase cost'
        pool = Pool()
        Currency = pool.get('currency.currency')
        PurchaseLine = pool.get('purchase.line')
        InvoiceLine = pool.get('account.invoice.line')
        Invoice = pool.get('account.invoice')
        Company = pool.get('company.company')

        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        purchase_line = PurchaseLine.__table__()
        invoice_line = InvoiceLine.__table__()
        invoice = Invoice.__table__()
        company = Company.__table__()

        amounts = defaultdict(Decimal)
        work_ids = [w.id for w in works]
        work2currency = {}
        iline2work = {}
        for sub_ids in grouped_slice(work_ids):
            where = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.join(
                purchase_line, condition=purchase_line.work == table.id).join(
                    invoice_line,
                    condition=invoice_line.origin ==
                    Concat('purchase.line,', purchase_line.id)).join(
                        invoice, condition=invoice_line.invoice == invoice.id
                    ).select(invoice_line.id,
                             table.id,
                             where=where
                             & ~invoice.state.in_(['draft', 'cancelled'])))
            iline2work.update(cursor)

            cursor.execute(
                *table.join(company, condition=table.company == company.id
                            ).select(table.id, company.currency, where=where))
            work2currency.update(cursor)

        currencies = Currency.browse(set(work2currency.values()))
        id2currency = {c.id: c for c in currencies}

        invoice_lines = InvoiceLine.browse(list(iline2work.keys()))
        for invoice_line in invoice_lines:
            invoice = invoice_line.invoice
            work_id = iline2work[invoice_line.id]
            currency_id = work2currency[work_id]
            currency = id2currency[currency_id]
            if currency != invoice.currency:
                with Transaction().set_context(date=invoice.currency_date):
                    amount = Currency.compute(invoice.currency,
                                              invoice_line.amount, currency)
            else:
                amount = invoice_line.amount
            amounts[work_id] += amount
        return amounts

    @classmethod
    def _get_revenue(cls, works):
        revenues = dict.fromkeys(map(int, works), Decimal(0))
        for work in works:
            if not work.list_price:
                continue
            if work.price_list_hour:
                revenue = work.company.currency.round(
                    work.list_price * Decimal(str(work.effort_hours)))
            else:
                revenue = work.list_price
            revenues[work.id] = work.company.currency.round(revenue)
        return revenues

    @fields.depends('company')
    def on_change_with_currency(self, name=None):
        if self.company:
            return self.company.currency.id

    @fields.depends('product', 'company')
    def on_change_product(self):
        pool = Pool()
        User = pool.get('res.user')
        ModelData = pool.get('ir.model.data')
        Uom = pool.get('product.uom')
        Currency = pool.get('currency.currency')

        if not self.product:
            return

        if self.price_list_hour:
            hour_uom = Uom(ModelData.get_id('product', 'uom_hour'))
            self.list_price = Uom.compute_price(self.product.default_uom,
                                                self.product.list_price,
                                                hour_uom)
        else:
            self.list_price = self.product.list_price

        if self.company:
            user = User(Transaction().user)
            if user.company != self.company:
                if user.company.currency != self.company.currency:
                    self.list_price = Currency.compute(user.company.currency,
                                                       self.list_price,
                                                       self.company.currency,
                                                       round=False)

        self.list_price = round_price(self.list_price)

    @property
    def price_list_hour(self):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        Category = pool.get('product.uom.category')
        if not self.product:
            return
        time = Category(ModelData.get_id('product', 'uom_cat_time'))
        return self.product.default_uom_category == time
コード例 #11
0
ファイル: sale.py プロジェクト: tryton/sale_advance_payment
class AdvancePaymentCondition(ModelSQL, ModelView):
    "Advance Payment Condition"
    __name__ = 'sale.advance_payment.condition'
    _rec_name = 'description'

    _states = {
        'readonly': Eval('sale_state') != 'draft',
    }

    sale = fields.Many2One('sale.sale',
                           'Sale',
                           required=True,
                           ondelete='CASCADE',
                           select=True,
                           states={
                               'readonly': ((Eval('sale_state') != 'draft')
                                            & Bool(Eval('sale'))),
                           })
    description = fields.Char("Description", required=True, states=_states)
    amount = Monetary("Amount",
                      currency='currency',
                      digits='currency',
                      states=_states)
    account = fields.Many2One('account.account',
                              "Account",
                              required=True,
                              domain=[
                                  ('type.unearned_revenue', '=', True),
                                  ('company', '=', Eval('sale_company')),
                              ],
                              states=_states)
    block_supply = fields.Boolean("Block Supply", states=_states)
    block_shipping = fields.Boolean("Block Shipping", states=_states)
    invoice_delay = fields.TimeDelta("Invoice Delay", states=_states)

    invoice_lines = fields.One2Many('account.invoice.line',
                                    'origin',
                                    "Invoice Lines",
                                    readonly=True)
    completed = fields.Function(fields.Boolean("Completed"), 'get_completed')

    sale_state = fields.Function(
        fields.Selection('get_sale_states', "Sale State"),
        'on_change_with_sale_state')
    sale_company = fields.Function(
        fields.Many2One('company.company', "Company"),
        'on_change_with_sale_company')
    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')

    del _states

    @classmethod
    def __setup__(cls):
        super(AdvancePaymentCondition, cls).__setup__()
        cls._order.insert(0, ('amount', 'ASC'))

    @classmethod
    def get_sale_states(cls):
        pool = Pool()
        Sale = pool.get('sale.sale')
        return Sale.fields_get(['state'])['state']['selection']

    @fields.depends('sale', '_parent_sale.state')
    def on_change_with_sale_state(self, name=None):
        if self.sale:
            return self.sale.state

    @fields.depends('sale', '_parent_sale.company')
    def on_change_with_sale_company(self, name=None):
        if self.sale and self.sale.company:
            return self.sale.company.id

    @fields.depends('sale', '_parent_sale.currency')
    def on_change_with_currency(self, name=None):
        if self.sale and self.sale.currency:
            return self.sale.currency.id

    @classmethod
    def copy(cls, conditions, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('invoice_lines', [])
        return super(AdvancePaymentCondition, cls).copy(conditions, default)

    def create_invoice(self):
        invoice = self.sale._get_invoice_sale()
        invoice.invoice_date = self.sale.sale_date
        if self.invoice_delay:
            invoice.invoice_date += self.invoice_delay
        invoice.payment_term = None

        invoice_lines = self.get_invoice_advance_payment_lines(invoice)
        if not invoice_lines:
            return None
        invoice.lines = invoice_lines
        invoice.save()

        invoice.update_taxes()
        return invoice

    def get_invoice_advance_payment_lines(self, invoice):
        pool = Pool()
        InvoiceLine = pool.get('account.invoice.line')

        advance_amount = self._get_advance_amount()
        advance_amount += self._get_ignored_amount()
        if advance_amount >= self.amount:
            return []

        invoice_line = InvoiceLine()
        invoice_line.invoice = invoice
        invoice_line.type = 'line'
        invoice_line.quantity = 1
        invoice_line.account = self.account
        invoice_line.unit_price = self.amount - advance_amount
        invoice_line.description = self.description
        invoice_line.origin = self
        invoice_line.company = self.sale.company
        invoice_line.currency = self.sale.currency
        # Set taxes
        invoice_line.on_change_account()
        return [invoice_line]

    def _get_advance_amount(self):
        return sum(l.amount for l in self.invoice_lines
                   if l.invoice.state != 'cancelled')

    def _get_ignored_amount(self):
        skips = {l for i in self.sale.invoices_recreated for l in i.lines}
        return sum(l.amount for l in self.invoice_lines
                   if l.invoice.state == 'cancelled' and l not in skips)

    def get_completed(self, name):
        advance_amount = 0
        lines_ignored = set(l for i in self.sale.invoices_ignored
                            for l in i.lines)
        for l in self.invoice_lines:
            if l.invoice.state == 'paid' or l in lines_ignored:
                advance_amount += l.amount
        return advance_amount >= self.amount
コード例 #12
0
class CategoryTree(ModelSQL, ModelView):
    "Stock Reporting Margin per Category"
    __name__ = 'stock.reporting.margin.category.tree'

    name = fields.Function(fields.Char("Name"), 'get_name')
    parent = fields.Many2One('stock.reporting.margin.category.tree', "Parent")
    children = fields.One2Many('stock.reporting.margin.category.tree',
                               'parent', "Children")
    cost = fields.Function(
        Monetary(lazy_gettext('stock.msg_stock_reporting_cost'),
                 currency='currency',
                 digits='currency'), 'get_total')
    revenue = fields.Function(
        Monetary(lazy_gettext('stock.msg_stock_reporting_revenue'),
                 currency='currency',
                 digits='currency'), 'get_total')
    profit = fields.Function(
        Monetary(lazy_gettext('stock.msg_stock_reporting_profit'),
                 currency='currency',
                 digits='currency'), 'get_total')
    margin = fields.Function(
        Monetary(lazy_gettext('stock.msg_stock_reporting_margin'),
                 digits=(14, 4)), 'get_margin')

    currency = fields.Function(
        fields.Many2One('currency.currency',
                        lazy_gettext('stock.msg_stock_reporting_currency')),
        'get_currency')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._order.insert(0, ('name', 'ASC'))

    @classmethod
    def table_query(cls):
        pool = Pool()
        Category = pool.get('product.category')
        return Category.__table__()

    @classmethod
    def get_name(cls, categories, name):
        pool = Pool()
        Category = pool.get('product.category')
        categories = Category.browse(categories)
        return {c.id: c.name for c in categories}

    @classmethod
    def order_name(cls, tables):
        pool = Pool()
        Category = pool.get('product.category')
        table, _ = tables[None]
        if 'category' not in tables:
            category = Category.__table__()
            tables['category'] = {
                None: (category, table.id == category.id),
            }
        return Category.name.convert_order('name', tables['category'],
                                           Category)

    def time_series_all(self):
        return []

    @classmethod
    def get_total(cls, categories, names):
        pool = Pool()
        ReportingCategory = pool.get('stock.reporting.margin.category')
        table = cls.__table__()
        reporting_category = ReportingCategory.__table__()
        cursor = Transaction().connection.cursor()

        categories = cls.search([
            ('parent', 'child_of', [c.id for c in categories]),
        ])
        ids = [c.id for c in categories]
        parents = {}
        reporting_categories = []
        for sub_ids in grouped_slice(ids):
            sub_ids = list(sub_ids)
            where = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.select(table.id, table.parent, where=where))
            parents.update(cursor)

            where = reduce_ids(reporting_category.id, sub_ids)
            cursor.execute(
                *reporting_category.select(reporting_category.id, where=where))
            reporting_categories.extend(r for r, in cursor)

        result = {}
        reporting_categories = ReportingCategory.browse(reporting_categories)
        for name in names:
            values = dict.fromkeys(ids, 0)
            values.update(
                (c.id, getattr(c, name)) for c in reporting_categories)
            result[name] = cls._sum_tree(categories, values, parents)
        return result

    @classmethod
    def _sum_tree(cls, categories, values, parents):
        result = values.copy()
        categories = set((c.id for c in categories))
        leafs = categories - set(parents.values())
        while leafs:
            for category in leafs:
                categories.remove(category)
                parent = parents.get(category)
                if parent in result:
                    result[parent] += result[category]
            next_leafs = set(categories)
            for category in categories:
                parent = parents.get(category)
                if not parent:
                    continue
                if parent in next_leafs and parent in categories:
                    next_leafs.remove(parent)
            leafs = next_leafs
        return result

    def get_margin(self, name):
        digits = self.__class__.margin.digits
        if self.profit is not None and self.revenue:
            return (self.profit / self.revenue).quantize(
                Decimal(1) / 10**digits[1])

    def get_currency(self, name):
        pool = Pool()
        Company = pool.get('company.company')
        company = Transaction().context.get('company')
        if company:
            return Company(company).currency.id

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree/field[@name="profit"]', 'visual',
             If(Eval('profit', 0) < 0, 'danger', '')),
            ('/tree/field[@name="margin"]', 'visual',
             If(Eval('margin', 0) < 0, 'danger', '')),
        ]
コード例 #13
0
ファイル: sale_reporting.py プロジェクト: tryton/sale
class ProductCategoryTree(ModelSQL, ModelView):
    "Sale Reporting per Product Category"
    __name__ = 'sale.reporting.product.category.tree'

    name = fields.Function(fields.Char("Name"), 'get_name')
    parent = fields.Many2One('sale.reporting.product.category.tree', "Parent")
    children = fields.One2Many('sale.reporting.product.category.tree',
                               'parent', "Children")
    revenue = fields.Function(Monetary("Revenue", digits='currency'),
                              'get_total')

    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"), 'get_currency')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._order.insert(0, ('name', 'ASC'))

    @classmethod
    def table_query(cls):
        pool = Pool()
        Category = pool.get('product.category')
        return Category.__table__()

    @classmethod
    def get_name(cls, categories, name):
        pool = Pool()
        Category = pool.get('product.category')
        categories = Category.browse(categories)
        return {c.id: c.name for c in categories}

    @classmethod
    def order_name(cls, tables):
        pool = Pool()
        Category = pool.get('product.category')
        table, _ = tables[None]
        if 'category' not in tables:
            category = Category.__table__()
            tables['category'] = {
                None: (category, table.id == category.id),
            }
        return Category.name.convert_order('name', tables['category'],
                                           Category)

    def time_series_all(self):
        return []

    @classmethod
    def get_total(cls, categories, names):
        pool = Pool()
        ReportingProductCategory = pool.get('sale.reporting.product.category')
        table = cls.__table__()
        reporting_product_category = ReportingProductCategory.__table__()
        cursor = Transaction().connection.cursor()

        categories = cls.search([
            ('parent', 'child_of', [c.id for c in categories]),
        ])
        ids = [c.id for c in categories]
        parents = {}
        reporting_product_categories = []
        for sub_ids in grouped_slice(ids):
            sub_ids = list(sub_ids)
            where = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.select(table.id, table.parent, where=where))
            parents.update(cursor)

            where = reduce_ids(reporting_product_category.id, sub_ids)
            cursor.execute(*reporting_product_category.select(
                reporting_product_category.id, where=where))
            reporting_product_categories.extend(r for r, in cursor)

        result = {}
        reporting_product_categories = ReportingProductCategory.browse(
            reporting_product_categories)
        for name in names:
            values = dict.fromkeys(ids, 0)
            values.update(
                (c.id, getattr(c, name)) for c in reporting_product_categories)
            result[name] = cls._sum_tree(categories, values, parents)
        return result

    @classmethod
    def _sum_tree(cls, categories, values, parents):
        result = values.copy()
        categories = set((c.id for c in categories))
        leafs = categories - set(parents.values())
        while leafs:
            for category in leafs:
                categories.remove(category)
                parent = parents.get(category)
                if parent in result:
                    result[parent] += result[category]
            next_leafs = set(categories)
            for category in categories:
                parent = parents.get(category)
                if not parent:
                    continue
                if parent in next_leafs and parent in categories:
                    next_leafs.remove(parent)
            leafs = next_leafs
        return result

    def get_currency(self, name):
        pool = Pool()
        Company = pool.get('company.company')
        company = Transaction().context.get('company')
        if company:
            return Company(company).currency.id
コード例 #14
0
class StatementRule(sequence_ordered(), ModelSQL, ModelView):
    "Account Statement Rule"
    __name__ = 'account.statement.rule'

    name = fields.Char("Name")

    company = fields.Many2One('company.company', "Company")
    journal = fields.Many2One('account.statement.journal',
                              "Journal",
                              domain=[
                                  If(Eval('company'),
                                     ('company', '=', Eval('company')), ()),
                              ],
                              depends=['company'])
    amount_low = Monetary("Amount Low",
                          currency='currency',
                          digits='currency',
                          domain=[
                              If(Eval('amount_high'), [
                                  'OR',
                                  ('amount_low', '=', None),
                                  ('amount_low', '<=', Eval('amount_high')),
                              ], [])
                          ],
                          depends=['amount_high'])
    amount_high = Monetary("Amount High",
                           currency='currency',
                           digits='currency',
                           domain=[
                               If(Eval('amount_low'), [
                                   'OR',
                                   ('amount_high', '=', None),
                                   ('amount_high', '>=', Eval('amount_low')),
                               ], [])
                           ],
                           depends=['amount_low'])
    description = fields.Char(
        "Description",
        help="The regular expression the description is searched with.\n"
        "It may define the groups named:\n"
        "'party', 'bank_account', 'invoice'.")
    information_rules = fields.One2Many('account.statement.rule.information',
                                        'rule', "Information Rules")

    lines = fields.One2Many('account.statement.rule.line', 'rule', "Lines")

    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')

    @fields.depends('journal')
    def on_change_with_currency(self, name=None):
        if self.journal:
            return self.journal.currency.id

    def match(self, origin):
        keywords = {}
        if self.company and self.company != origin.company:
            return False
        if self.journal and self.journal != origin.statement.journal:
            return False
        if self.amount_low is not None and self.amount_low > origin.amount:
            return False
        if self.amount_high is not None and self.amount_high < origin.amount:
            return False
        if self.information_rules:
            for irule in self.information_rules:
                result = irule.match(origin)
                if isinstance(result, dict):
                    keywords.update(result)
                elif not result:
                    return False
        if self.description:
            result = re.search(self.description, origin.description or '')
            if not result:
                return False
            keywords.update(result.groupdict())
        keywords.update(amount=origin.amount, pending=origin.amount)
        return keywords

    def apply(self, origin, keywords):
        keywords = keywords.copy()
        for rule_line in self.lines:
            line = rule_line.get_line(origin, keywords)
            if not line:
                return
            keywords['pending'] -= line.amount
            yield line
コード例 #15
0
ファイル: sale.py プロジェクト: tryton/sale_promotion
class Promotion(
        DeactivableMixin, ModelSQL, ModelView, MatchMixin):
    'Sale Promotion'
    __name__ = 'sale.promotion'

    name = fields.Char('Name', translate=True, required=True)
    company = fields.Many2One(
        'company.company', "Company", required=True, select=True,
        states={
            'readonly': Eval('id', 0) > 0,
            })
    start_date = fields.Date('Start Date',
        domain=['OR',
            ('start_date', '<=', If(~Eval('end_date', None),
                    datetime.date.max,
                    Eval('end_date', datetime.date.max))),
            ('start_date', '=', None),
            ])
    end_date = fields.Date('End Date',
        domain=['OR',
            ('end_date', '>=', If(~Eval('start_date', None),
                    datetime.date.min,
                    Eval('start_date', datetime.date.min))),
            ('end_date', '=', None),
            ])
    price_list = fields.Many2One('product.price_list', 'Price List',
        ondelete='CASCADE',
        domain=[
            ('company', '=', Eval('company', -1)),
            ])

    amount = Monetary("Amount", currency='currency', digits='currency')
    currency = fields.Many2One(
        'currency.currency', "Currency",
        states={
            'required': Bool(Eval('amount', 0)),
            })
    untaxed_amount = fields.Boolean(
        "Untaxed Amount",
        states={
            'invisible': ~Eval('amount'),
            })

    quantity = fields.Float('Quantity', digits='unit')
    unit = fields.Many2One('product.uom', 'Unit',
        states={
            'required': Bool(Eval('quantity', 0)),
            })
    products = fields.Many2Many(
        'sale.promotion-product.product', 'promotion', 'product', "Products",
        context={
            'company': Eval('company', -1),
            },
        depends={'company'})
    categories = fields.Many2Many(
        'sale.promotion-product.category', 'promotion', 'category',
        "Categories",
        context={
            'company': Eval('company', -1),
            },
        depends={'company'})
    formula = fields.Char('Formula', required=True,
        help=('Python expression that will be evaluated with:\n'
            '- unit_price: the original unit_price'))

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @classmethod
    def default_untaxed_amount(cls):
        return False

    @classmethod
    def validate_fields(cls, promotions, field_names):
        super().validate_fields(promotions, field_names)
        cls.check_formula(promotions, field_names)

    @classmethod
    def check_formula(cls, promotions, field_names=None):
        if field_names and 'formula' not in field_names:
            return
        for promotion in promotions:
            context = promotion.get_context_formula(None)
            try:
                unit_price = promotion.get_unit_price(**context)
                if not isinstance(unit_price, Decimal):
                    raise ValueError('Not a Decimal')
            except Exception as exception:
                raise FormulaError(
                    gettext('sale_promotion.msg_invalid_formula',
                        formula=promotion.formula,
                        promotion=promotion.rec_name,
                        exception=exception)) from exception

    @classmethod
    def _promotions_domain(cls, sale):
        pool = Pool()
        Date = pool.get('ir.date')
        with Transaction().set_context(company=sale.company.id):
            sale_date = sale.sale_date or Date.today()
        return [
            ['OR',
                ('start_date', '<=', sale_date),
                ('start_date', '=', None),
                ],
            ['OR',
                ('end_date', '=', None),
                ('end_date', '>=', sale_date),
                ],
            ['OR',
                ('price_list', '=', None),
                ('price_list', '=',
                    sale.price_list.id if sale.price_list else None),
                ],
            ('company', '=', sale.company.id),
            ]

    @classmethod
    def get_promotions(cls, sale, pattern=None):
        'Yield promotions that apply to sale'
        promotions = cls.search(cls._promotions_domain(sale))
        if pattern is None:
            pattern = {}
        for promotion in promotions:
            ppattern = pattern.copy()
            ppattern.update(promotion.get_pattern(sale))
            if promotion.match(ppattern):
                yield promotion

    def get_pattern(self, sale):
        pool = Pool()
        Currency = pool.get('currency.currency')
        Uom = pool.get('product.uom')
        Sale = pool.get('sale.sale')
        pattern = {}
        if self.currency:
            amount = self.get_sale_amount(Sale(sale.id))
            pattern['amount'] = Currency.compute(
                sale.currency, amount, self.currency)
        if self.unit:
            quantity = 0
            for line in sale.lines:
                if self.is_valid_sale_line(line):
                    quantity += Uom.compute_qty(line.unit, line.quantity,
                        self.unit)
            pattern['quantity'] = quantity
        return pattern

    def match(self, pattern):
        def sign(amount):
            return Decimal(1).copy_sign(amount)
        if 'quantity' in pattern:
            pattern = pattern.copy()
            if (self.quantity or 0) > pattern.pop('quantity'):
                return False
        if 'amount' in pattern:
            pattern = pattern.copy()
            amount = pattern.pop('amount')
            if (sign(self.amount or 0) * sign(amount) >= 0
                    and abs(self.amount or 0) > abs(amount)):
                return False
        return super().match(pattern)

    def get_sale_amount(self, sale):
        if self.untaxed_amount:
            return sale.untaxed_amount
        else:
            return sale.total_amount

    def is_valid_sale_line(self, line):

        def parents(categories):
            for category in categories:
                while category:
                    yield category
                    category = category.parent

        if line.quantity <= 0 or line.unit_price <= 0:
            return False
        elif self.unit and line.unit.category != self.unit.category:
            return False
        elif self.products and line.product not in self.products:
            return False
        elif (self.categories
                and not set(parents(line.product.categories_all)).intersection(
                    self.categories)):
            return False
        else:
            return True

    def apply(self, sale):
        applied = False
        for line in sale.lines:
            if line.type != 'line':
                continue
            if not self.is_valid_sale_line(line):
                continue
            context = self.get_context_formula(line)
            new_price = self.get_unit_price(**context)
            if new_price is not None:
                if new_price < 0:
                    new_price = Decimal(0)
                if line.unit_price >= new_price:
                    line.unit_price = round_price(new_price)
                    line.promotion = self
                    applied = True
        if applied:
            sale.lines = sale.lines  # Trigger the change

    def get_context_formula(self, sale_line):
        pool = Pool()
        Product = pool.get('product.product')
        if sale_line:
            with Transaction().set_context(
                    sale_line._get_context_sale_price()):
                prices = Product.get_sale_price([sale_line.product])
            unit_price = prices[sale_line.product.id]
        else:
            unit_price = Decimal(0)
        return {
            'names': {
                'unit_price': unit_price if unit_price is not None else Null(),
                },
            }

    def get_unit_price(self, **context):
        'Return unit price (as Decimal)'
        context.setdefault('functions', {})['Decimal'] = Decimal
        return max(simple_eval(decistmt(self.formula), **context), Decimal(0))
コード例 #16
0
ファイル: dunning.py プロジェクト: tryton/account_dunning
class Dunning(ModelSQL, ModelView):
    'Account Dunning'
    __name__ = 'account.dunning'
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              help="Make the dunning belong to the company.",
                              select=True,
                              states=_STATES)
    line = fields.Many2One('account.move.line',
                           'Line',
                           required=True,
                           help="The receivable line to dun for.",
                           domain=[
                               ('account.type.receivable', '=', True),
                               ('account.company', '=', Eval('company', -1)),
                               [
                                   'OR',
                                   ('debit', '>', 0),
                                   ('credit', '<', 0),
                               ],
                           ],
                           states=_STATES)
    procedure = fields.Many2One('account.dunning.procedure',
                                'Procedure',
                                required=True,
                                states=_STATES)
    level = fields.Many2One('account.dunning.level',
                            'Level',
                            required=True,
                            domain=[
                                ('procedure', '=', Eval('procedure', -1)),
                            ],
                            states=_STATES)
    date = fields.Date("Date",
                       readonly=True,
                       states={
                           'invisible': Eval('state') == 'draft',
                       },
                       help="When the dunning reached the level.")
    age = fields.Function(
        fields.TimeDelta("Age",
                         states={
                             'invisible': Eval('state') == 'draft',
                         },
                         help="How long the dunning has been at the level."),
        'get_age')
    blocked = fields.Boolean(
        'Blocked', help="Check to block further levels of the procedure.")
    state = fields.Selection([
        ('draft', 'Draft'),
        ('waiting', "Waiting"),
        ('final', "Final"),
    ],
                             'State',
                             readonly=True,
                             sort=False)
    active = fields.Function(fields.Boolean('Active'),
                             'get_active',
                             searcher='search_active')
    party = fields.Function(fields.Many2One('party.party',
                                            'Party',
                                            context={
                                                'company': Eval('company', -1),
                                            },
                                            depends={'company'}),
                            'get_line_field',
                            searcher='search_line_field')
    amount = fields.Function(
        Monetary("Amount", currency='currency', digits='currency'),
        'get_amount')
    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"), 'get_line_field')
    maturity_date = fields.Function(fields.Date('Maturity Date'),
                                    'get_line_field',
                                    searcher='search_line_field')
    amount_second_currency = fields.Function(
        Monetary('Amount Second Currency',
                 currency='second_currency',
                 digits='second_currency',
                 states={
                     'invisible': Eval('currency') == Eval('second_currency'),
                 }), 'get_amount_second_currency')
    second_currency = fields.Function(
        fields.Many2One('currency.currency', 'Second Currency'),
        'get_second_currency')

    @classmethod
    def __setup__(cls):
        super(Dunning, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints = [
            ('line_unique', Unique(table, table.line),
             'account_dunning.msg_dunning_line_unique'),
        ]
        cls._active_field = 'active'
        cls._buttons.update({
            'reschedule': {
                'invisible': ~Eval('active', True),
                'depends': ['active'],
            },
        })

    @classmethod
    def __register__(cls, module):
        dunning = cls.__table__()
        super().__register__(module)
        cursor = Transaction().connection.cursor()

        # Migration from 4.8: rename done state into waiting
        cursor.execute(*dunning.update([dunning.state], ['waiting'],
                                       where=dunning.state == 'done'))

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_state():
        return 'draft'

    def get_age(self, name):
        pool = Pool()
        Date = pool.get('ir.date')
        if self.date:
            with Transaction().set_context(company=self.company.id):
                return Date.today() - self.date

    @classmethod
    def order_age(cls, tables):
        pool = Pool()
        Date = pool.get('ir.date')
        table, _ = tables[None]
        return [Literal(Date.today()) - table.date]

    def get_active(self, name):
        return not self.line.reconciliation

    def get_line_field(self, name):
        value = getattr(self.line, name)
        if isinstance(value, Model):
            return value.id
        else:
            return value

    @classmethod
    def search_line_field(cls, name, clause):
        return [('line.' + clause[0], ) + tuple(clause[1:])]

    def get_amount(self, name):
        return self.line.debit - self.line.credit

    def get_amount_second_currency(self, name):
        amount = self.line.debit - self.line.credit
        if self.line.amount_second_currency:
            return self.line.amount_second_currency.copy_sign(amount)
        else:
            return amount

    def get_second_currency(self, name):
        if self.line.second_currency:
            return self.line.second_currency.id
        else:
            return self.line.account.company.currency.id

    @classmethod
    def search_active(cls, name, clause):
        reverse = {
            '=': '!=',
            '!=': '=',
        }
        if clause[1] in reverse:
            if clause[2]:
                return [('line.reconciliation', clause[1], None)]
            else:
                return [('line.reconciliation', reverse[clause[1]], None)]
        else:
            return []

    @classmethod
    def _overdue_line_domain(cls, date):
        return [
            ('account.type.receivable', '=', True),
            ('dunnings', '=', None),
            ('maturity_date', '<=', date),
            [
                'OR',
                ('debit', '>', 0),
                ('credit', '<', 0),
            ],
            ('party', '!=', None),
            ('reconciliation', '=', None),
        ]

    @classmethod
    def generate_dunnings(cls, date=None):
        pool = Pool()
        Date = pool.get('ir.date')
        MoveLine = pool.get('account.move.line')

        if date is None:
            date = Date.today()

        set_level = defaultdict(list)
        with Transaction().set_context(_check_access=True):
            dunnings = cls.search([
                ('state', '=', 'waiting'),
                ('blocked', '=', False),
            ])
        dunnings = cls.browse(dunnings)
        for dunning in dunnings:
            procedure = dunning.procedure
            levels = procedure.levels
            levels = levels[levels.index(dunning.level) + 1:]
            if levels:
                for level in levels:
                    if level.test(dunning.line, date):
                        break
                else:
                    level = dunning.level
                if level != dunning.level:
                    set_level[level].append(dunning)
            else:
                set_level[None].append(dunning)
        to_write = []
        for level, dunnings in set_level.items():
            if level:
                to_write.extend((dunnings, {
                    'level': level.id,
                    'state': 'draft',
                    'date': None,
                }))
            else:
                to_write.extend((dunnings, {
                    'state': 'final',
                    'date': Date.today(),
                }))
        if to_write:
            cls.write(*to_write)

        with Transaction().set_context(_check_access=True):
            lines = MoveLine.search(cls._overdue_line_domain(date))
        lines = MoveLine.browse(lines)
        dunnings = (cls._get_dunning(line, date) for line in lines)
        cls.save([d for d in dunnings if d])

    @classmethod
    def _get_dunning(cls, line, date):
        procedure = line.dunning_procedure
        if not procedure:
            return
        for level in procedure.levels:
            if level.test(line, date):
                break
        else:
            return
        return cls(
            line=line,
            procedure=procedure,
            level=level,
        )

    @classmethod
    def process(cls, dunnings):
        pool = Pool()
        Date = pool.get('ir.date')
        for company, c_dunnings in groupby(dunnings, key=lambda d: d.company):
            with Transaction().set_context(company=company.id):
                today = Date.today()
            cls.write([
                d for d in c_dunnings if not d.blocked and d.state == 'draft'
            ], {
                'state': 'waiting',
                'date': today,
            })

    @classmethod
    @ModelView.button_action('account_dunning.act_reschedule_dunning_wizard')
    def reschedule(cls, dunnings):
        pass
コード例 #17
0
class QuotationLine(ModelSQL, ModelView):
    "Purchase Request For Quotation Line"
    __name__ = 'purchase.request.quotation.line'

    supplier = fields.Function(fields.Many2One('party.party', 'Supplier'),
                               'get_supplier')
    supply_date = fields.Date('Supply Date',
                              help="When it should be delivered.")
    product = fields.Function(fields.Many2One('product.product', 'Product'),
                              'get_product',
                              searcher='search_product')
    description = fields.Text('Description',
                              states={'required': ~Eval('product')})
    quantity = fields.Float("Quantity", digits='unit', required=True)
    unit = fields.Many2One(
        'product.uom',
        'Unit',
        ondelete='RESTRICT',
        states={
            'required': Bool(Eval('product')),
        },
        domain=[
            If(Bool(Eval('product_uom_category')),
               ('category', '=', Eval('product_uom_category')),
               ('category', '!=', -1)),
        ])
    product_uom_category = fields.Function(
        fields.Many2One('product.uom.category', 'Product Uom Category'),
        'on_change_with_product_uom_category')
    unit_price = Monetary("Unit Price",
                          currency='currency',
                          digits=price_digits)
    currency = fields.Many2One('currency.currency',
                               'Currency',
                               states={
                                   'required': Bool(Eval('unit_price')),
                               })
    request = fields.Many2One(
        'purchase.request',
        'Request',
        ondelete='CASCADE',
        select=True,
        required=True,
        domain=[
            If(
                Eval('quotation_state') == 'draft',
                ('state', 'in', ['draft', 'quotation', 'received']),
                (),
            ),
        ],
        states={'readonly': Eval('quotation_state') != 'draft'},
        help="The request which this line belongs to.")
    quotation = fields.Many2One('purchase.request.quotation',
                                'Quotation',
                                ondelete='CASCADE',
                                required=True,
                                domain=[
                                    ('supplier', '=', Eval('supplier')),
                                ])
    quotation_state = fields.Function(fields.Selection('get_quotation_state',
                                                       'Quotation State'),
                                      'on_change_with_quotation_state',
                                      searcher='search_quotation_state')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('quotation')

    @staticmethod
    def order_quotation_state(tables):
        pool = Pool()
        Quotation = pool.get('purchase.request.quotation')
        quotation_line, _ = tables[None]
        quotation = Quotation.__table__()
        tables['purchase.request.quotation'] = {
            None: (quotation, quotation_line.quotation == quotation.id),
        }
        return [
            Case((quotation.state == 'received', 0), else_=1), quotation.state
        ]

    def get_supplier(self, name):
        if self.quotation and self.quotation.supplier:
            return self.quotation.supplier.id

    @fields.depends('request', '_parent_request.product',
                    '_parent_request.description', '_parent_request.quantity',
                    '_parent_request.uom', '_parent_request.company',
                    '_parent_request.supply_date')
    def on_change_request(self):
        if self.request:
            self.product = self.request.product
            self.description = self.request.description
            self.quantity = self.request.quantity
            self.unit = self.request.uom
            if self.request.company:
                self.currency = self.request.company.currency
            self.supply_date = self.request.supply_date or datetime.date.max

    @fields.depends('product')
    def on_change_with_product_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom_category.id

    @classmethod
    def get_quotation_state(cls):
        pool = Pool()
        Quotation = pool.get('purchase.request.quotation')
        return (Quotation.fields_get(['state'])['state']['selection'])

    @fields.depends('quotation', '_parent_quotation.state')
    def on_change_with_quotation_state(self, name=None):
        pool = Pool()
        Quotation = pool.get('purchase.request.quotation')
        if self.quotation:
            return self.quotation.state
        return Quotation.default_state()

    @classmethod
    def search_quotation_state(cls, name, clause):
        return [('quotation.state', ) + tuple(clause[1:])]

    def get_rec_name(self, name):
        return '%s - %s' % (self.quotation.rec_name, self.supplier.rec_name)

    @classmethod
    def search_rec_name(cls, name, clause):
        names = clause[2].split(' - ', 1)
        res = [('quotation', clause[1], names[0])]
        if len(names) != 1 and names[1]:
            res.append(('supplier', clause[1], names[1]))
        return res

    @classmethod
    def delete(cls, quotationlines):
        pool = Pool()
        Request = pool.get('purchase.request')
        requests = [l.request for l in quotationlines]
        super(QuotationLine, cls).delete(quotationlines)
        Request.update_state(requests)

    def get_product(self, name):
        if self.request and self.request.product:
            return self.request.product.id

    @classmethod
    def search_product(cls, name, clause):
        return [('request.' + clause[0], ) + tuple(clause[1:])]
コード例 #18
0
ファイル: product.py プロジェクト: tryton/sale
class Product(metaclass=PoolMeta):
    __name__ = 'product.product'

    sale_price_uom = fields.Function(Monetary(
            "Sale Price", digits=price_digits), 'get_sale_price_uom')

    @classmethod
    def get_sale_price_uom(cls, products, name):
        quantity = Transaction().context.get('quantity') or 0
        return cls.get_sale_price(products, quantity=quantity)

    def _get_sale_unit_price(self, quantity=0):
        return self.list_price_used

    @classmethod
    def get_sale_price(cls, products, quantity=0):
        '''
        Return the sale price for products and quantity.
        It uses if exists from the context:
            uom: the unit of measure or the sale uom of the product
            currency: the currency id for the returned price
        '''
        pool = Pool()
        Uom = pool.get('product.uom')
        User = pool.get('res.user')
        Currency = pool.get('currency.currency')
        Date = pool.get('ir.date')

        today = Date.today()
        prices = {}

        assert len(products) == len(set(products)), "Duplicate products"

        uom = None
        if Transaction().context.get('uom'):
            uom = Uom(Transaction().context.get('uom'))

        currency = None
        if Transaction().context.get('currency'):
            currency = Currency(Transaction().context.get('currency'))

        user = User(Transaction().user)

        for product in products:
            unit_price = product._get_sale_unit_price(quantity=quantity)
            if unit_price is not None:
                if uom and product.default_uom.category == uom.category:
                    unit_price = Uom.compute_price(
                        product.default_uom, unit_price, uom)
                else:
                    unit_price = Uom.compute_price(
                        product.default_uom, unit_price, product.sale_uom)
            if currency and user.company and unit_price is not None:
                if user.company.currency != currency:
                    date = Transaction().context.get('sale_date') or today
                    with Transaction().set_context(date=date):
                        unit_price = Currency.compute(
                            user.company.currency, unit_price,
                            currency, round=False)
            prices[product.id] = unit_price
        return prices

    @property
    def lead_time_used(self):
        pool = Pool()
        Configuration = pool.get('product.configuration')
        if self.lead_time is None:
            with Transaction().set_context(self._context):
                config = Configuration(1)
                return config.get_multivalue('default_lead_time')
        else:
            return self.lead_time

    def compute_shipping_date(self, date=None):
        '''
        Compute the shipping date at the given date
        '''
        Date = Pool().get('ir.date')

        if not date:
            with Transaction().set_context(context=self._context):
                date = Date.today()

        lead_time = self.lead_time_used
        if lead_time is None:
            return datetime.date.max
        return date + lead_time
コード例 #19
0
class Journal(DeactivableMixin, ModelSQL, ModelView, CompanyMultiValueMixin):
    'Journal'
    __name__ = 'account.journal'
    name = fields.Char('Name', size=None, required=True, translate=True)
    code = fields.Char('Code', size=None)
    type = fields.Selection([
        ('general', "General"),
        ('revenue', "Revenue"),
        ('expense', "Expense"),
        ('cash', "Cash"),
        ('situation', "Situation"),
        ('write-off', "Write-Off"),
    ],
                            'Type',
                            required=True)
    sequence = fields.MultiValue(
        fields.Many2One('ir.sequence',
                        "Sequence",
                        domain=[
                            ('sequence_type', '=',
                             Id('account', 'sequence_type_account_journal')),
                            ('company', 'in',
                             [Eval('context', {}).get('company', -1), None]),
                        ],
                        states={
                            'required':
                            Bool(Eval('context', {}).get('company', -1)),
                        }))
    sequences = fields.One2Many('account.journal.sequence', 'journal',
                                "Sequences")
    debit = fields.Function(
        Monetary("Debit", currency='currency', digits='currency'),
        'get_debit_credit_balance')
    credit = fields.Function(
        Monetary("Credit", currency='currency', digits='currency'),
        'get_debit_credit_balance')
    balance = fields.Function(
        Monetary("Balance", currency='currency', digits='currency'),
        'get_debit_credit_balance')

    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"), 'get_currency')

    @classmethod
    def __setup__(cls):
        super(Journal, cls).__setup__()
        cls._order.insert(0, ('name', 'ASC'))

    @classmethod
    def default_sequence(cls, **pattern):
        return cls.multivalue_model('sequence').default_sequence()

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        code_value = clause[2]
        if clause[1].endswith('like'):
            code_value = lstrip_wildcard(clause[2])
        return [
            bool_op,
            ('code', clause[1], code_value) + tuple(clause[3:]),
            (cls._rec_name, ) + tuple(clause[1:]),
        ]

    @classmethod
    def get_currency(cls, journals, name):
        pool = Pool()
        Company = pool.get('company.company')
        company_id = Transaction().context.get('company')
        if company_id:
            company = Company(company_id)
            currency_id = company.currency.id
        else:
            currency_id = None
        return dict.fromkeys([j.id for j in journals], currency_id)

    @classmethod
    def get_debit_credit_balance(cls, journals, names):
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Move = pool.get('account.move')
        Account = pool.get('account.account')
        AccountType = pool.get('account.account.type')
        Company = pool.get('company.company')
        context = Transaction().context
        cursor = Transaction().connection.cursor()

        result = {}
        ids = [j.id for j in journals]
        for name in ['debit', 'credit', 'balance']:
            result[name] = dict.fromkeys(ids, 0)

        company_id = Transaction().context.get('company')
        if not company_id:
            return result
        company = Company(company_id)

        line = MoveLine.__table__()
        move = Move.__table__()
        account = Account.__table__()
        account_type = AccountType.__table__()
        where = ((move.date >= context.get('start_date'))
                 & (move.date <= context.get('end_date'))
                 & ~account_type.receivable
                 & ~account_type.payable
                 & (move.company == company.id))
        for sub_journals in grouped_slice(journals):
            sub_journals = list(sub_journals)
            red_sql = reduce_ids(move.journal, [j.id for j in sub_journals])
            query = line.join(
                move, 'LEFT', condition=line.move == move.id).join(
                    account, 'LEFT',
                    condition=line.account == account.id).join(
                        account_type,
                        'LEFT',
                        condition=account.type == account_type.id).select(
                            move.journal,
                            Sum(line.debit),
                            Sum(line.credit),
                            where=where & red_sql,
                            group_by=move.journal)
            cursor.execute(*query)
            for journal_id, debit, credit in cursor:
                # SQLite uses float for SUM
                if not isinstance(debit, Decimal):
                    debit = Decimal(str(debit))
                if not isinstance(credit, Decimal):
                    credit = Decimal(str(credit))
                result['debit'][journal_id] = company.currency.round(debit)
                result['credit'][journal_id] = company.currency.round(credit)
                result['balance'][journal_id] = company.currency.round(debit -
                                                                       credit)
        return result

    @classmethod
    def write(cls, *args):
        pool = Pool()
        Move = pool.get('account.move')
        actions = iter(args)
        for journals, values in zip(actions, actions):
            if 'type' in values:
                for sub_journals in grouped_slice(journals):
                    moves = Move.search(
                        [('journal', 'in', [j.id for j in sub_journals]),
                         ('state', '=', 'posted')],
                        order=[],
                        limit=1)
                    if moves:
                        move, = moves
                        raise AccessError(
                            gettext('account.msg_journal_account_moves',
                                    journal=move.journal.rec_name))
        super().write(*args)
コード例 #20
0
class Asset(Workflow, ModelSQL, ModelView):
    'Asset'
    __name__ = 'account.asset'
    _rec_name = 'number'
    number = fields.Char('Number', readonly=True, select=True)
    product = fields.Many2One('product.product',
                              'Product',
                              required=True,
                              states={
                                  'readonly': (Eval('lines', [0]) |
                                               (Eval('state') != 'draft')),
                              },
                              context={
                                  'company': Eval('company', None),
                              },
                              depends={'company'},
                              domain=[
                                  ('type', '=', 'assets'),
                                  ('depreciable', '=', True),
                              ])
    supplier_invoice_line = fields.Many2One(
        'account.invoice.line',
        'Supplier Invoice Line',
        domain=[
            If(
                ~Eval('product', None),
                ('product', '=', -1),
                ('product', '=', Eval('product', -1)),
            ),
            ('invoice.type', '=', 'in'),
            [
                'OR',
                ('company', '=', Eval('company', -1)),
                ('invoice.company', '=', Eval('company', -1)),
            ],
        ],
        states={
            'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
        })
    customer_invoice_line = fields.Function(
        fields.Many2One('account.invoice.line', 'Customer Invoice Line'),
        'get_customer_invoice_line')
    account_journal = fields.Many2One('account.journal',
                                      'Journal',
                                      states={
                                          'readonly': Eval('state') != 'draft',
                                      },
                                      domain=[('type', '=', 'asset')],
                                      required=True)
    company = fields.Many2One('company.company',
                              'Company',
                              states={
                                  'readonly': Eval('state') != 'draft',
                              },
                              required=True)
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'),
        'on_change_with_currency')
    quantity = fields.Float("Quantity",
                            digits='unit',
                            states={
                                'readonly':
                                (Bool(Eval('supplier_invoice_line', 1))
                                 | Eval('lines', [0])
                                 | (Eval('state') != 'draft')),
                            })
    unit = fields.Many2One('product.uom',
                           'Unit',
                           states={
                               'readonly': (Bool(Eval('product'))
                                            | (Eval('state') != 'draft')),
                           })
    value = Monetary("Value",
                     currency='currency',
                     digits='currency',
                     states={
                         'readonly':
                         (Eval('lines', [0]) | (Eval('state') != 'draft')),
                     },
                     required=True,
                     help="The value of the asset when purchased.")
    depreciated_amount = Monetary(
        "Depreciated Amount",
        currency='currency',
        digits='currency',
        domain=[
            ('depreciated_amount', '<=', Eval('value')),
        ],
        states={
            'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
        },
        required=True,
        help="The amount already depreciated at the start date.")
    depreciating_value = fields.Function(
        Monetary("Depreciating Value",
                 currency='currency',
                 digits='currency',
                 help="The value of the asset at the start date."),
        'on_change_with_depreciating_value')
    residual_value = Monetary(
        "Residual Value",
        currency='currency',
        digits='currency',
        required=True,
        domain=[
            ('residual_value', '<=', Eval('depreciating_value')),
        ],
        states={
            'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
        })
    purchase_date = fields.Date('Purchase Date',
                                states={
                                    'readonly':
                                    (Bool(Eval('supplier_invoice_line', 1))
                                     | Eval('lines', [0])
                                     | (Eval('state') != 'draft')),
                                },
                                required=True)
    start_date = fields.Date(
        'Start Date',
        states={
            'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
        },
        required=True,
        domain=[('start_date', '<=', Eval('end_date', None))])
    end_date = fields.Date(
        'End Date',
        states={
            'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
        },
        required=True,
        domain=[('end_date', '>=', Eval('start_date', None))])
    depreciation_method = fields.Selection(
        [
            ('linear', 'Linear'),
        ],
        'Depreciation Method',
        states={
            'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
        },
        required=True)
    frequency = fields.Selection([
        ('monthly', 'Monthly'),
        ('yearly', 'Yearly'),
    ],
                                 'Frequency',
                                 required=True,
                                 states={
                                     'readonly': (Eval('lines', [0]) |
                                                  (Eval('state') != 'draft')),
                                 })
    state = fields.Selection([
        ('draft', 'Draft'),
        ('running', 'Running'),
        ('closed', 'Closed'),
    ],
                             "State",
                             readonly=True,
                             sort=False)
    lines = fields.One2Many('account.asset.line',
                            'asset',
                            'Lines',
                            readonly=True)
    move = fields.Many2One('account.move',
                           'Account Move',
                           readonly=True,
                           domain=[
                               ('company', '=', Eval('company', -1)),
                           ])
    update_moves = fields.Many2Many('account.asset-update-account.move',
                                    'asset',
                                    'move',
                                    'Update Moves',
                                    readonly=True,
                                    domain=[
                                        ('company', '=', Eval('company', -1)),
                                    ],
                                    states={
                                        'invisible': ~Eval('update_moves'),
                                    })
    comment = fields.Text('Comment')
    revisions = fields.One2Many('account.asset.revision',
                                'asset',
                                "Revisions",
                                readonly=True)

    @classmethod
    def __setup__(cls):
        super(Asset, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints = [
            ('invoice_line_uniq', Unique(table, table.supplier_invoice_line),
             'account_asset.msg_asset_invoice_line_unique'),
        ]
        cls._transitions |= set((
            ('draft', 'running'),
            ('running', 'closed'),
            ('running', 'draft'),
        ))
        cls._buttons.update({
            'draft': {
                'invisible': (Eval('lines', [])
                              | (Eval('state') != 'running')),
                'depends': ['state'],
            },
            'run': {
                'invisible': Eval('state') != 'draft',
                'depends': ['state'],
            },
            'close': {
                'invisible': Eval('state') != 'running',
                'depends': ['state'],
            },
            'create_lines': {
                'invisible': ~Eval('state').in_(['draft', 'running']),
                'depends': ['state'],
            },
            'clear_lines': {
                'invisible': (~Eval('lines', [0])
                              | ~Eval('state').in_(['draft', 'running'])),
                'depends': ['state'],
            },
            'update': {
                'invisible': Eval('state') != 'running',
                'depends': ['state'],
            },
        })

    @classmethod
    def __register__(cls, module_name):
        table_h = cls.__table_handler__(module_name)

        # Migration from 3.8: rename reference into number
        if table_h.column_exist('reference'):
            table_h.column_rename('reference', 'number')
        super(Asset, cls).__register__(module_name)

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    def default_frequency(cls, **pattern):
        pool = Pool()
        Configuration = pool.get('account.configuration')
        return Configuration(1).get_multivalue('asset_frequency', **pattern)

    @staticmethod
    def default_depreciation_method():
        return 'linear'

    @classmethod
    def default_depreciated_amount(cls):
        return Decimal(0)

    @classmethod
    def default_residual_value(cls):
        return Decimal(0)

    @staticmethod
    def default_start_date():
        return Pool().get('ir.date').today()

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @fields.depends('company')
    def on_change_company(self):
        self.frequency = self.default_frequency(
            company=self.company.id if self.company else None)

    @staticmethod
    def default_account_journal():
        Journal = Pool().get('account.journal')
        journals = Journal.search([
            ('type', '=', 'asset'),
        ])
        if len(journals) == 1:
            return journals[0].id
        return None

    @fields.depends('value', 'depreciated_amount')
    def on_change_with_depreciating_value(self, name=None):
        if self.value is not None and self.depreciated_amount is not None:
            return self.value - self.depreciated_amount
        else:
            return Decimal(0)

    @fields.depends('company')
    def on_change_with_currency(self, name=None):
        if self.company:
            return self.company.currency.id

    @fields.depends('supplier_invoice_line', 'unit')
    def on_change_supplier_invoice_line(self):
        pool = Pool()
        Currency = pool.get('currency.currency')
        Unit = Pool().get('product.uom')

        if not self.supplier_invoice_line:
            self.quantity = None
            self.value = None
            self.start_date = self.default_start_date()
            return

        invoice_line = self.supplier_invoice_line
        invoice = invoice_line.invoice
        if invoice.company.currency != invoice.currency:
            with Transaction().set_context(date=invoice.currency_date):
                self.value = Currency.compute(invoice.currency,
                                              invoice_line.amount,
                                              invoice.company.currency)
        else:
            self.value = invoice_line.amount
        if invoice.invoice_date:
            self.purchase_date = invoice.invoice_date
            self.start_date = invoice.invoice_date
            if invoice_line.product.depreciation_duration:
                duration = relativedelta.relativedelta(
                    months=invoice_line.product.depreciation_duration, days=-1)
                self.end_date = self.start_date + duration

        if not self.unit:
            self.quantity = invoice_line.quantity
        else:
            self.quantity = Unit.compute_qty(invoice_line.unit,
                                             invoice_line.quantity, self.unit)

    @fields.depends('product')
    def on_change_with_unit(self):
        if not self.product:
            return None
        return self.product.default_uom.id

    @fields.depends('end_date', 'product', 'start_date')
    def on_change_with_end_date(self):
        if (all(getattr(self, k, None) for k in ('product', 'start_date'))
                and not self.end_date):
            if self.product.depreciation_duration:
                duration = relativedelta.relativedelta(months=int(
                    self.product.depreciation_duration),
                                                       days=-1)
                return self.start_date + duration
        return self.end_date

    @classmethod
    def get_customer_invoice_line(cls, assets, name):
        InvoiceLine = Pool().get('account.invoice.line')
        invoice_lines = InvoiceLine.search([
            ('asset', 'in', [a.id for a in assets]),
        ])
        result = dict((a.id, None) for a in assets)
        result.update(dict((l.asset.id, l.id) for l in invoice_lines))
        return result

    def get_depreciated_amount(self):
        lines = [
            line.depreciation for line in self.lines
            if line.move and line.move.state == 'posted'
        ]
        return sum(lines, Decimal(0))

    def compute_move_dates(self):
        """
        Returns all the remaining dates at which asset depreciation movement
        will be issued.
        """
        pool = Pool()
        Config = pool.get('account.configuration')
        config = Config(1)

        start_date = max([self.start_date] + [l.date for l in self.lines])
        delta = relativedelta.relativedelta(self.end_date, start_date)
        # dateutil >= 2.0 has replace __nonzero__ by __bool__ which doesn't
        # work in Python < 3
        if delta == relativedelta.relativedelta():
            if not self.lines:
                return [self.end_date]
            else:
                return []
        if self.frequency == 'monthly':
            rule = rrule.rrule(rrule.MONTHLY,
                               dtstart=self.start_date,
                               bymonthday=int(
                                   config.get_multivalue(
                                       'asset_bymonthday',
                                       company=self.company.id)))
        elif self.frequency == 'yearly':
            rule = rrule.rrule(
                rrule.YEARLY,
                dtstart=self.start_date,
                bymonth=int(
                    config.get_multivalue('asset_bymonth',
                                          company=self.company.id)),
                bymonthday=int(
                    config.get_multivalue('asset_bymonthday',
                                          company=self.company.id)))
        dates = [
            d.date() for d in rule.between(date2datetime(start_date),
                                           date2datetime(self.end_date))
        ]
        dates.append(self.end_date)
        return dates

    def compute_depreciation(self, amount, date, dates):
        """
        Returns the depreciation amount for an asset on a certain date.
        """
        if self.depreciation_method == 'linear':
            start_date = max(
                [self.start_date - relativedelta.relativedelta(days=1)] +
                [l.date for l in self.lines])
            first_delta = normalized_delta(start_date, dates[0])
            if len(dates) > 1:
                last_delta = normalized_delta(dates[-2], dates[-1])
            else:
                last_delta = first_delta
            if self.frequency == 'monthly':
                _, first_ndays = calendar.monthrange(dates[0].year,
                                                     dates[0].month)
                if (calendar.isleap(dates[0].year)
                        and dates[0].month == February):
                    first_ndays -= 1
                _, last_ndays = calendar.monthrange(dates[-1].year,
                                                    dates[-1].month)
                if (calendar.isleap(dates[-1].year)
                        and dates[-1].month == February):
                    last_ndays -= 1
            elif self.frequency == 'yearly':
                first_ndays = last_ndays = 365
            first_ratio = (Decimal(min(first_delta.days, first_ndays)) /
                           Decimal(first_ndays))
            last_ratio = (Decimal(min(last_delta.days, last_ndays)) /
                          Decimal(last_ndays))
            depreciation = amount / (len(dates) - 2 + first_ratio + last_ratio)
            if date == dates[0]:
                depreciation *= first_ratio
            elif date == dates[-1]:
                depreciation *= last_ratio
            return self.company.currency.round(depreciation)

    def depreciate(self):
        """
        Returns all the depreciation amounts still to be accounted.
        """
        Line = Pool().get('account.asset.line')
        amounts = {}
        dates = self.compute_move_dates()
        depreciated_amount = self.get_depreciated_amount()
        amount = (self.depreciating_value - depreciated_amount -
                  self.residual_value)
        if amount <= 0:
            return amounts
        residual_value, acc_depreciation = (amount, depreciated_amount +
                                            self.depreciated_amount)
        asset_line = None
        for date in dates:
            depreciation = self.compute_depreciation(amount, date, dates)
            amounts[date] = asset_line = Line(
                acquired_value=self.value,
                depreciable_basis=amount,
            )
            if depreciation > residual_value:
                asset_line.depreciation = residual_value
                asset_line.accumulated_depreciation = (acc_depreciation +
                                                       residual_value)
                break
            else:
                residual_value -= depreciation
                acc_depreciation += depreciation
                asset_line.depreciation = depreciation
                asset_line.accumulated_depreciation = acc_depreciation
        else:
            if residual_value > 0 and asset_line is not None:
                asset_line.depreciation += residual_value
                asset_line.accumulated_depreciation += residual_value
        for asset_line in amounts.values():
            asset_line.actual_value = (self.value -
                                       asset_line.accumulated_depreciation)
        return amounts

    @classmethod
    @ModelView.button
    def create_lines(cls, assets):
        pool = Pool()
        Line = pool.get('account.asset.line')

        lines = []
        for asset in assets:
            for date, line in asset.depreciate().items():
                line.asset = asset.id
                line.date = date
                lines.append(line)
        Line.save(lines)

    @classmethod
    @ModelView.button
    def clear_lines(cls, assets):
        Line = Pool().get('account.asset.line')

        lines_to_delete = []
        for asset in assets:
            for line in asset.lines:
                if not line.move or line.move.state != 'posted':
                    lines_to_delete.append(line)
        Line.delete(lines_to_delete)

    @classmethod
    @ModelView.button_action('account_asset.wizard_update')
    def update(cls, assets):
        pass

    def get_move(self, line):
        """
        Return the account.move generated by an asset line.
        """
        pool = Pool()
        Period = pool.get('account.period')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')

        period_id = Period.find(self.company.id, line.date)
        with Transaction().set_context(date=line.date):
            expense_line = MoveLine(
                credit=0,
                debit=line.depreciation,
                account=self.product.account_expense_used,
            )
            depreciation_line = MoveLine(
                debit=0,
                credit=line.depreciation,
                account=self.product.account_depreciation_used,
            )

        return Move(
            company=self.company,
            origin=line,
            period=period_id,
            journal=self.account_journal,
            date=line.date,
            lines=[expense_line, depreciation_line],
        )

    @classmethod
    def create_moves(cls, assets, date):
        """
        Creates all account move on assets before a date.
        """
        pool = Pool()
        Move = pool.get('account.move')
        Line = pool.get('account.asset.line')

        cls.create_lines(assets)

        moves = []
        lines = []
        for asset_ids in grouped_slice(assets):
            lines += Line.search([
                ('asset', 'in', list(asset_ids)),
                ('date', '<=', date),
                ('move', '=', None),
            ])
        for line in lines:
            moves.append(line.asset.get_move(line))
        Move.save(moves)
        for move, line in zip(moves, lines):
            line.move = move
        Line.save(lines)
        Move.post(moves)

    def get_closing_move(self, account, date=None):
        """
        Returns closing move values.
        """
        pool = Pool()
        Period = pool.get('account.period')
        Date = pool.get('ir.date')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')

        if date is None:
            with Transaction().set_context(company=self.company.id):
                date = Date.today()
        period_id = Period.find(self.company.id, date)
        if self.supplier_invoice_line:
            account_asset = self.supplier_invoice_line.account.current()
        else:
            account_asset = self.product.account_asset_used

        asset_line = MoveLine(
            debit=0,
            credit=self.value,
            account=account_asset,
        )
        depreciation_line = MoveLine(
            debit=self.get_depreciated_amount() + self.depreciated_amount,
            credit=0,
            account=self.product.account_depreciation_used,
        )
        lines = [asset_line, depreciation_line]
        square_amount = asset_line.credit - depreciation_line.debit
        if square_amount:
            if not account:
                account = self.product.account_revenue_used
            counter_part_line = MoveLine(
                debit=square_amount if square_amount > 0 else 0,
                credit=-square_amount if square_amount < 0 else 0,
                account=account,
            )
            lines.append(counter_part_line)
        return Move(
            company=self.company,
            origin=self,
            period=period_id,
            journal=self.account_journal,
            date=date,
            lines=lines,
        )

    @classmethod
    def set_number(cls, assets):
        '''
        Fill the number field with asset sequence.
        '''
        pool = Pool()
        Config = pool.get('account.configuration')

        config = Config(1)
        for asset in assets:
            if asset.number:
                continue
            asset.number = config.get_multivalue(
                'asset_sequence', company=asset.company.id).get()
        cls.save(assets)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, assets):
        for asset in assets:
            if asset.lines:
                raise AccessError(
                    gettext('account_asset.msg_draft_lines',
                            asset=asset.rec_name))

    @classmethod
    @ModelView.button
    @Workflow.transition('running')
    def run(cls, assets):
        cls.set_number(assets)
        cls.create_lines(assets)

    @classmethod
    @ModelView.button
    @Workflow.transition('closed')
    def close(cls, assets, account=None, date=None):
        """
        Close the assets.
        If account is provided, it will be used instead of the expense account.
        """
        Move = Pool().get('account.move')

        cls.clear_lines(assets)
        moves = []
        for asset in assets:
            moves.append(asset.get_closing_move(account, date=date))
        Move.save(moves)
        for move, asset in zip(moves, assets):
            asset.move = move
        cls.save(assets)
        Move.post(moves)

    def get_rec_name(self, name):
        return '%s - %s' % (self.number, self.product.rec_name)

    @classmethod
    def search_rec_name(cls, name, clause):
        names = clause[2].split(' - ', 1)
        res = [('number', clause[1], names[0])]
        if len(names) != 1 and names[1]:
            res.append(('product', clause[1], names[1]))
        return res

    @classmethod
    def copy(cls, assets, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('lines', [])
        default.setdefault('update_moves', [])
        default.setdefault('number', None)
        default.setdefault('supplier_invoice_line', None)
        default.setdefault('move')
        default.setdefault('revisions', [])
        return super(Asset, cls).copy(assets, default=default)

    @classmethod
    def delete(cls, assets):
        for asset in assets:
            if asset.state != 'draft':
                raise AccessError(
                    gettext('account_asset.msg_delete_draft',
                            asset=asset.rec_name))
        return super(Asset, cls).delete(assets)
コード例 #21
0
ファイル: statement.py プロジェクト: tryton/account_statement
class LineGroup(ModelSQL, ModelView):
    'Account Statement Line Group'
    __name__ = 'account.statement.line.group'
    _rec_name = 'number'
    statement = fields.Many2One('account.statement', 'Statement')
    journal = fields.Function(fields.Many2One('account.statement.journal',
            'Journal'), 'get_journal', searcher='search_journal')
    number = fields.Char('Number')
    date = fields.Date('Date')
    amount = Monetary(
        "Amount", currency='currency', digits='currency')
    currency = fields.Function(fields.Many2One('currency.currency',
            'Currency'), 'get_currency')
    party = fields.Many2One('party.party', 'Party')
    move = fields.Many2One('account.move', 'Move')

    @classmethod
    def __setup__(cls):
        super(LineGroup, cls).__setup__()
        cls.__access__.add('statement')
        cls._order.insert(0, ('date', 'DESC'))

    @classmethod
    def _grouped_columns(cls, line):
        return [
            Max(line.statement).as_('statement'),
            Max(line.number).as_('number'),
            Max(line.date).as_('date'),
            Sum(line.amount).as_('amount'),
            Max(line.party).as_('party'),
            ]

    @classmethod
    def table_query(cls):
        pool = Pool()
        Move = pool.get('account.move')
        Line = pool.get('account.statement.line')
        move = Move.__table__()
        line = Line.__table__()

        std_columns = [
            move.id,
            move.create_uid,
            move.create_date,
            move.write_uid,
            move.write_date,
            ]

        columns = (std_columns + [move.id.as_('move')]
            + cls._grouped_columns(line))
        return move.join(line,
            condition=move.id == line.move
            ).select(*columns,
                where=move.origin.like(Statement.__name__ + ',%'),
                group_by=std_columns + [move.id]
                )

    def get_journal(self, name):
        return self.statement.journal.id

    @classmethod
    def search_journal(cls, name, clause):
        return [('statement.' + clause[0],) + tuple(clause[1:])]

    def get_currency(self, name):
        return self.statement.journal.currency.id
コード例 #22
0
ファイル: sale_reporting.py プロジェクト: tryton/sale
class Abstract(ModelSQL):

    company = fields.Many2One('company.company',
                              lazy_gettext("sale.msg_sale_reporting_company"))
    number = fields.Integer(
        lazy_gettext("sale.msg_sale_reporting_number"),
        help=lazy_gettext("sale.msg_sale_reporting_number_help"))
    revenue = Monetary(lazy_gettext("sale.msg_sale_reporting_revenue"),
                       digits='currency',
                       currency='currency')
    revenue_trend = fields.Function(
        fields.Char(lazy_gettext("sale.msg_sale_reporting_revenue_trend")),
        'get_trend')
    time_series = None

    currency = fields.Function(
        fields.Many2One('currency.currency',
                        lazy_gettext("sale.msg_sale_reporting_currency")),
        'get_currency')

    @classmethod
    def table_query(cls):
        from_item, tables, withs = cls._joins()
        return from_item.select(*cls._columns(tables, withs),
                                where=cls._where(tables, withs),
                                group_by=cls._group_by(tables, withs),
                                with_=withs.values())

    @classmethod
    def _sale_line(cls, length, index, company_id=None):
        pool = Pool()
        Line = pool.get('sale.line')
        Sale = pool.get('sale.sale')

        line = Line.__table__()
        sale = Sale.__table__()

        return (line.join(sale, condition=line.sale == sale.id).select(
            (line.id * length + index).as_('id'),
            line.product.as_('product'),
            Coalesce(line.actual_quantity, line.quantity).as_('quantity'),
            line.unit_price.as_('unit_price'),
            Concat('sale.sale,', line.sale).as_('order'),
            sale.sale_date.as_('date'),
            sale.company.as_('company'),
            sale.currency.as_('currency'),
            sale.party.as_('customer'),
            sale.warehouse.as_('location'),
            sale.shipment_address.as_('shipment_address'),
            where=sale.state.in_(cls._sale_states())
            & (sale.company == company_id),
        ))

    @classmethod
    def _lines(cls):
        return [cls._sale_line]

    @classmethod
    def _joins(cls):
        pool = Pool()
        Company = pool.get('company.company')
        Currency = pool.get('currency.currency')
        context = Transaction().context

        tables = {}
        company = context.get('company')
        lines = cls._lines()
        tables['line'] = line = Union(*(l(len(lines), i, company)
                                        for i, l in enumerate(lines)))
        tables['line.company'] = company = Company.__table__()
        withs = {}
        currency_sale = With(query=Currency.currency_rate_sql())
        withs['currency_sale'] = currency_sale
        currency_company = With(query=Currency.currency_rate_sql())
        withs['currency_company'] = currency_company

        from_item = (line.join(
            currency_sale,
            condition=(line.currency == currency_sale.currency)
            & (currency_sale.start_date <= line.date)
            &
            ((currency_sale.end_date == Null)
             | (currency_sale.end_date >= line.date))).join(
                 company, condition=line.company == company.id).join(
                     currency_company,
                     condition=(company.currency == currency_company.currency)
                     & (currency_company.start_date <= line.date)
                     & ((currency_company.end_date == Null)
                        | (currency_company.end_date >= line.date))))
        return from_item, tables, withs

    @classmethod
    def _columns(cls, tables, withs):
        line = tables['line']
        currency_company = withs['currency_company']
        currency_sale = withs['currency_sale']

        revenue = cls.revenue.sql_cast(
            Sum(line.quantity * line.unit_price * currency_company.rate /
                currency_sale.rate))
        return [
            cls._column_id(tables, withs).as_('id'),
            Literal(0).as_('create_uid'),
            CurrentTimestamp().as_('create_date'),
            cls.write_uid.sql_cast(Literal(Null)).as_('write_uid'),
            cls.write_date.sql_cast(Literal(Null)).as_('write_date'),
            line.company.as_('company'),
            revenue.as_('revenue'),
            Count(line.order, distinct=True).as_('number'),
        ]

    @classmethod
    def _column_id(cls, tables, withs):
        line = tables['line']
        return Min(line.id)

    @classmethod
    def _group_by(cls, tables, withs):
        line = tables['line']
        return [line.company]

    @classmethod
    def _where(cls, tables, withs):
        pool = Pool()
        Location = pool.get('stock.location')
        context = Transaction().context
        line = tables['line']

        where = Literal(True)
        from_date = context.get('from_date')
        if from_date:
            where &= line.date >= from_date
        to_date = context.get('to_date')
        if to_date:
            where &= line.date <= to_date
        warehouse = context.get('warehouse')
        if warehouse:
            locations = Location.search([
                ('parent', 'child_of', warehouse),
            ],
                                        query=True)
            where &= line.location.in_(locations)
        return where

    @classmethod
    def _sale_states(cls):
        return ['confirmed', 'processing', 'done']

    @property
    def time_series_all(self):
        delta = self._period_delta()
        for ts, next_ts in pairwise(self.time_series or []):
            yield ts
            if delta and next_ts:
                date = ts.date + delta
                while date < next_ts.date:
                    yield None
                    date += delta

    @classmethod
    def _period_delta(cls):
        context = Transaction().context
        return {
            'year': relativedelta(years=1),
            'month': relativedelta(months=1),
            'day': relativedelta(days=1),
        }.get(context.get('period'))

    def get_trend(self, name):
        name = name[:-len('_trend')]
        if pygal:
            chart = pygal.Line()
            chart.add('', [
                getattr(ts, name) if ts else 0 for ts in self.time_series_all
            ])
            return chart.render_sparktext()

    def get_currency(self, name):
        return self.company.currency.id
コード例 #23
0
class Abstract(ModelSQL, ModelView):

    company = fields.Many2One(
        'company.company', lazy_gettext('stock.msg_stock_reporting_company'))
    cost = Monetary(lazy_gettext('stock.msg_stock_reporting_cost'),
                    currency='currency',
                    digits='currency')
    revenue = Monetary(lazy_gettext('stock.msg_stock_reporting_revenue'),
                       currency='currency',
                       digits='currency')
    profit = Monetary(lazy_gettext('stock.msg_stock_reporting_profit'),
                      currency='currency',
                      digits='currency')
    margin = fields.Numeric(lazy_gettext('stock.msg_stock_reporting_margin'),
                            digits=(14, 4),
                            states={
                                'invisible': ~Eval('margin'),
                            })
    margin_trend = fields.Function(
        fields.Char(lazy_gettext('stock.msg_stock_reporting_margin_trend')),
        'get_trend')
    time_series = None

    currency = fields.Many2One(
        'currency.currency',
        lazy_gettext('stock.msg_stock_reporting_currency'))

    @classmethod
    def table_query(cls):
        from_item, tables, withs = cls._joins()
        return from_item.select(*cls._columns(tables, withs),
                                where=cls._where(tables, withs),
                                group_by=cls._group_by(tables, withs),
                                with_=withs.values())

    @classmethod
    def _joins(cls):
        pool = Pool()
        Company = pool.get('company.company')
        Currency = pool.get('currency.currency')
        Move = pool.get('stock.move')
        Location = pool.get('stock.location')

        tables = {}
        tables['move'] = move = Move.__table__()
        tables['move.company'] = company = Company.__table__()
        tables['move.company.currency'] = currency = Currency.__table__()
        tables['move.from_location'] = from_location = Location.__table__()
        tables['move.to_location'] = to_location = Location.__table__()
        withs = {}
        withs['currency_rate'] = currency_rate = With(
            query=Currency.currency_rate_sql())
        withs['currency_rate_company'] = currency_rate_company = With(
            query=Currency.currency_rate_sql())

        from_item = (move.join(
            currency_rate,
            condition=(move.currency == currency_rate.currency)
            & (currency_rate.start_date <= move.effective_date)
            & ((currency_rate.end_date == Null)
               | (currency_rate.end_date >= move.effective_date))
        ).join(company, condition=move.company == company.id).join(
            currency, condition=company.currency == currency.id).join(
                currency_rate_company,
                condition=(company.currency == currency_rate_company.currency)
                & (currency_rate_company.start_date <= move.effective_date)
                & ((currency_rate_company.end_date == Null)
                   | (currency_rate_company.end_date >= move.effective_date))
            ).join(from_location,
                   condition=(move.from_location == from_location.id)).join(
                       to_location,
                       condition=(move.to_location == to_location.id)))
        return from_item, tables, withs

    @classmethod
    def _columns(cls, tables, withs):
        move = tables['move']
        from_location = tables['move.from_location']
        to_location = tables['move.to_location']
        currency = tables['move.company.currency']

        sign = Case((from_location.type.in_(cls._to_location_types())
                     & to_location.type.in_(cls._from_location_types()), -1),
                    else_=1)
        cost = cls._column_cost(tables, withs, sign)
        revenue = cls._column_revenue(tables, withs, sign)
        profit = revenue - cost
        margin = Case((revenue != 0, profit / revenue), else_=Null)
        return [
            cls._column_id(tables, withs).as_('id'),
            Literal(0).as_('create_uid'),
            CurrentTimestamp().as_('create_date'),
            cls.write_uid.sql_cast(Literal(Null)).as_('write_uid'),
            cls.write_date.sql_cast(Literal(Null)).as_('write_date'),
            move.company.as_('company'),
            cls.cost.sql_cast(Round(cost, currency.digits)).as_('cost'),
            cls.revenue.sql_cast(Round(revenue,
                                       currency.digits)).as_('revenue'),
            cls.profit.sql_cast(Round(profit, currency.digits)).as_('profit'),
            cls.margin.sql_cast(Round(margin,
                                      cls.margin.digits[1])).as_('margin'),
            currency.id.as_('currency'),
        ]

    @classmethod
    def _column_id(cls, tables, withs):
        move = tables['move']
        return Min(move.id)

    @classmethod
    def _column_cost(cls, tables, withs, sign):
        move = tables['move']
        return Sum(sign * cls.cost.sql_cast(move.internal_quantity) *
                   Coalesce(move.cost_price, 0))

    @classmethod
    def _column_revenue(cls, tables, withs, sign):
        move = tables['move']
        currency = withs['currency_rate']
        currency_company = withs['currency_rate_company']
        return Sum(sign * cls.revenue.sql_cast(move.quantity) *
                   Coalesce(move.unit_price, 0) *
                   Coalesce(currency_company.rate / currency.rate, 0))

    @classmethod
    def _group_by(cls, tables, withs):
        move = tables['move']
        currency = tables['move.company.currency']
        return [move.company, currency.id, currency.digits]

    @classmethod
    def _where(cls, tables, withs):
        context = Transaction().context
        move = tables['move']
        from_location = tables['move.from_location']
        to_location = tables['move.to_location']

        where = move.company == context.get('company')
        where &= ((from_location.type.in_(cls._from_location_types())
                   & to_location.type.in_(cls._to_location_types()))
                  | (from_location.type.in_(cls._to_location_types())
                     & to_location.type.in_(cls._from_location_types())))
        where &= move.state == 'done'
        from_date = context.get('from_date')
        if from_date:
            where &= move.effective_date >= from_date
        to_date = context.get('to_date')
        if to_date:
            where &= move.effective_date <= to_date
        return where

    @classmethod
    def _from_location_types(cls):
        return ['storage', 'drop']

    @classmethod
    def _to_location_types(cls):
        types = ['customer']
        if Transaction().context.get('include_lost'):
            types += ['lost_found']
        return types

    @property
    def time_series_all(self):
        delta = self._period_delta()
        for ts, next_ts in pairwise(self.time_series or []):
            yield ts
            if delta and next_ts:
                date = ts.date + delta
                while date < next_ts.date:
                    yield None
                    date += delta

    @classmethod
    def _period_delta(cls):
        context = Transaction().context
        return {
            'year': relativedelta(years=1),
            'month': relativedelta(months=1),
            'day': relativedelta(days=1),
        }.get(context.get('period'))

    def get_trend(self, name):
        name = name[:-len('_trend')]
        if pygal:
            chart = pygal.Line()
            chart.add('', [
                getattr(ts, name) or 0 if ts else 0
                for ts in self.time_series_all
            ])
            return chart.render_sparktext()

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree/field[@name="profit"]', 'visual',
             If(Eval('profit', 0) < 0, 'danger', '')),
            ('/tree/field[@name="margin"]', 'visual',
             If(Eval('margin', 0) < 0, 'danger', '')),
        ]
コード例 #24
0
ファイル: statement.py プロジェクト: tryton/account_statement
class Origin(origin_mixin(_states), ModelSQL, ModelView):
    "Account Statement Origin"
    __name__ = 'account.statement.origin'
    _rec_name = 'number'

    lines = fields.One2Many(
        'account.statement.line', 'origin', "Lines",
        states={
            'readonly': ((Eval('statement_id', -1) < 0)
                | ~Eval('statement_state').in_(['draft', 'validated'])),
            },
        domain=[
            ('statement', '=', Eval('statement', -1)),
            ('date', '=', Eval('date', None)),
            ])
    statement_id = fields.Function(
        fields.Integer("Statement ID"), 'on_change_with_statement_id')
    pending_amount = fields.Function(Monetary(
            "Pending Amount", currency='currency', digits='currency'),
        'on_change_with_pending_amount', searcher='search_pending_amount')
    information = fields.Dict(
        'account.statement.origin.information', "Information", readonly=True)

    @classmethod
    def __register__(cls, module_name):
        table = cls.__table_handler__(module_name)

        # Migration from 5.0: rename informations into information
        table.column_rename('informations', 'information')

        super(Origin, cls).__register__(module_name)

    @fields.depends('statement', '_parent_statement.id')
    def on_change_with_statement_id(self, name=None):
        if self.statement:
            return self.statement.id
        return -1

    @fields.depends('lines', 'amount')
    def on_change_with_pending_amount(self, name=None):
        lines_amount = sum(
            getattr(l, 'amount') or Decimal(0) for l in self.lines)
        return (self.amount or Decimal(0)) - lines_amount

    @classmethod
    def search_pending_amount(cls, name, clause):
        pool = Pool()
        Line = pool.get('account.statement.line')
        table = cls.__table__()
        line = Line.__table__()

        _, operator, value = clause
        Operator = fields.SQL_OPERATORS[operator]

        query = (table.join(line, 'LEFT', condition=line.origin == table.id)
            .select(table.id,
                having=Operator(
                    table.amount - Coalesce(Sum(line.amount), 0), value),
                group_by=table.id))
        return [('id', 'in', query)]

    @classmethod
    def copy(cls, origins, default=None):
        default = default.copy() if default is not None else {}
        default.setdefault('lines')
        return super().copy(origins, default=default)
コード例 #25
0
ファイル: product.py プロジェクト: tryton/purchase
class ProductSupplierPrice(sequence_ordered(), ModelSQL, ModelView,
                           MatchMixin):
    'Product Supplier Price'
    __name__ = 'purchase.product_supplier.price'
    product_supplier = fields.Many2One('purchase.product_supplier',
                                       'Supplier',
                                       required=True,
                                       ondelete='CASCADE')
    quantity = fields.Float("Quantity",
                            required=True,
                            domain=[('quantity', '>=', 0)],
                            help='Minimal quantity.')
    unit_price = Monetary("Unit Price",
                          currency='currency',
                          required=True,
                          digits=price_digits)

    uom = fields.Function(
        fields.Many2One('product.uom',
                        'UOM',
                        help="The unit in which the quantity is specified."),
        'on_change_with_uom')
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'),
        'on_change_with_currency')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('product_supplier')

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        table = cls.__table_handler__(module_name)
        sql_table = cls.__table__()

        fill_sequence = not table.column_exist('sequence')

        super(ProductSupplierPrice, cls).__register__(module_name)

        # Migration from 3.2: replace quantity by sequence for order
        if fill_sequence:
            cursor.execute(
                *sql_table.update([sql_table.sequence], [sql_table.quantity]))

    @staticmethod
    def default_quantity():
        return 0.0

    @fields.depends('product_supplier', '_parent_product_supplier.product')
    def on_change_with_uom(self, name=None):
        if self.product_supplier and self.product_supplier.uom:
            return self.product_supplier.uom.id

    @fields.depends('product_supplier', '_parent_product_supplier.currency')
    def on_change_with_currency(self, name=None):
        if self.product_supplier and self.product_supplier.currency:
            return self.product_supplier.currency.id

    @staticmethod
    def get_pattern():
        return {}

    def match(self, quantity, uom, pattern):
        pool = Pool()
        Uom = pool.get('product.uom')
        test_quantity = Uom.compute_qty(self.product_supplier.uom,
                                        self.quantity, uom)
        if test_quantity > abs(quantity):
            return False
        return super(ProductSupplierPrice, self).match(pattern)
コード例 #26
0
ファイル: statement.py プロジェクト: tryton/account_statement
class Statement(Workflow, ModelSQL, ModelView):
    'Account Statement'
    __name__ = 'account.statement'

    _states = {'readonly': Eval('state') != 'draft'}
    _balance_states = _states.copy()
    _balance_states.update({
            'invisible': ~Eval('validation', '').in_(['balance']),
            'required': Eval('validation', '').in_(['balance']),
            })
    _amount_states = _states.copy()
    _amount_states.update({
            'invisible': ~Eval('validation', '').in_(['amount']),
            'required': Eval('validation', '').in_(['amount']),
            })
    _number_states = _states.copy()
    _number_states.update({
            'invisible': ~Eval('validation', '').in_(['number_of_lines']),
            'required': Eval('validation', '').in_(['number_of_lines']),
            })

    name = fields.Char('Name', required=True)
    company = fields.Many2One(
        'company.company', "Company", required=True, select=True,
        states=_states)
    journal = fields.Many2One('account.statement.journal', 'Journal',
        required=True, select=True,
        domain=[
            ('company', '=', Eval('company', -1)),
            ],
        states={
            'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
            })
    currency = fields.Function(fields.Many2One(
            'currency.currency', "Currency"), 'on_change_with_currency')
    date = fields.Date('Date', required=True, select=True)
    start_balance = Monetary(
        "Start Balance", currency='currency', digits='currency',
        states=_balance_states)
    end_balance = Monetary(
        "End Balance", currency='currency', digits='currency',
        states=_balance_states)
    balance = fields.Function(Monetary(
            "Balance", currency='currency', digits='currency',
            states=_balance_states),
        'on_change_with_balance')
    total_amount = Monetary(
        "Total Amount", currency='currency', digits='currency',
        states=_amount_states)
    number_of_lines = fields.Integer('Number of Lines',
        states=_number_states)
    lines = fields.One2Many('account.statement.line', 'statement',
        'Lines', states={
            'readonly': (Eval('state') != 'draft') | ~Eval('journal'),
            })
    origins = fields.One2Many('account.statement.origin', 'statement',
        "Origins", states={
            'readonly': Eval('state') != 'draft',
            })
    origin_file = fields.Binary(
        "Origin File", readonly=True,
        file_id=file_id, store_prefix=store_prefix)
    origin_file_id = fields.Char("Origin File ID", readonly=True)
    state = fields.Selection([
            ('draft', "Draft"),
            ('validated', "Validated"),
            ('cancelled', "Cancelled"),
            ('posted', "Posted"),
            ], "State", readonly=True, select=True, sort=False)
    validation = fields.Function(fields.Char('Validation'),
        'on_change_with_validation')
    to_reconcile = fields.Function(
        fields.Boolean("To Reconcile"), 'get_to_reconcile')

    del _states
    del _balance_states
    del _amount_states
    del _number_states

    @classmethod
    def __setup__(cls):
        super(Statement, cls).__setup__()
        cls._order[0] = ('id', 'DESC')
        cls._transitions |= set((
                ('draft', 'validated'),
                ('draft', 'cancelled'),
                ('validated', 'posted'),
                ('validated', 'cancelled'),
                ('cancelled', 'draft'),
                ))
        cls._buttons.update({
                'draft': {
                    'invisible': Eval('state') != 'cancelled',
                    'depends': ['state'],
                    },
                'validate_statement': {
                    'invisible': Eval('state') != 'draft',
                    'depends': ['state'],
                    },
                'post': {
                    'invisible': Eval('state') != 'validated',
                    'depends': ['state'],
                    },
                'cancel': {
                    'invisible': ~Eval('state').in_(['draft', 'validated']),
                    'depends': ['state'],
                    },
                'reconcile': {
                    'invisible': Eval('state').in_(['draft', 'cancelled']),
                    'readonly': ~Eval('to_reconcile'),
                    'depends': ['state', 'to_reconcile'],
                    },
                })
        cls.__rpc__.update({
                'post': RPC(
                    readonly=False, instantiate=0, fresh_session=True),
                })

    @classmethod
    def __register__(cls, module_name):
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        sql_table = cls.__table__()

        super(Statement, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)

        # Migration from 3.2: remove required on start/end balance
        table.not_null_action('start_balance', action='remove')
        table.not_null_action('end_balance', action='remove')

        # Migration from 3.2: add required name
        cursor.execute(*sql_table.update([sql_table.name],
                [sql_table.id.cast(cls.name.sql_type().base)],
                where=sql_table.name == Null))

        # Migration from 5.6: rename state cancel to cancelled
        cursor.execute(*sql_table.update(
                [sql_table.state], ['cancelled'],
                where=sql_table.state == 'cancel'))

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_date():
        Date = Pool().get('ir.date')
        return Date.today()

    @fields.depends('journal', 'state', 'lines')
    def on_change_journal(self):
        if not self.journal:
            return

        statements = self.search([
                ('journal', '=', self.journal.id),
                ], order=[
                ('date', 'DESC'),
                ('id', 'DESC'),
                ], limit=1)
        if not statements:
            return

        statement, = statements
        self.start_balance = statement.end_balance

    @fields.depends('journal')
    def on_change_with_currency(self, name=None):
        if self.journal:
            return self.journal.currency.id

    @fields.depends('start_balance', 'end_balance')
    def on_change_with_balance(self, name=None):
        return ((getattr(self, 'end_balance', 0) or 0)
            - (getattr(self, 'start_balance', 0) or 0))

    @fields.depends('origins', 'lines', 'journal', 'company')
    def on_change_origins(self):
        if not self.journal or not self.origins or not self.company:
            return
        if self.journal.currency != self.company.currency:
            return

        invoices = set()
        for line in self.lines:
            if (line.invoice
                    and line.invoice.currency == self.company.currency):
                invoices.add(line.invoice)
        for origin in self.origins:
            for line in origin.lines:
                if (line.invoice
                        and line.invoice.currency == self.company.currency):
                    invoices.add(line.invoice)
        invoice_id2amount_to_pay = {}
        for invoice in invoices:
            if invoice.type == 'out':
                sign = -1
            else:
                sign = 1
            invoice_id2amount_to_pay[invoice.id] = sign * invoice.amount_to_pay

        origins = list(self.origins)
        for origin in origins:
            lines = list(origin.lines)
            for line in lines:
                if (line.invoice
                        and line.id
                        and line.invoice.id in invoice_id2amount_to_pay):
                    amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
                    if (amount_to_pay
                            and getattr(line, 'amount', None)
                            and (line.amount >= 0) == (amount_to_pay <= 0)):
                        if abs(line.amount) > abs(amount_to_pay):
                            line.amount = amount_to_pay.copy_sign(line.amount)
                        else:
                            invoice_id2amount_to_pay[line.invoice.id] = (
                                line.amount + amount_to_pay)
                    else:
                        line.invoice = None
            origin.lines = lines
        self.origins = origins

    @fields.depends('lines', 'journal', 'company')
    def on_change_lines(self):
        pool = Pool()
        Line = pool.get('account.statement.line')
        if not self.journal or not self.lines or not self.company:
            return
        if self.journal.currency != self.company.currency:
            return

        invoices = set()
        for line in self.lines:
            if (line.invoice
                    and line.invoice.currency == self.company.currency):
                invoices.add(line.invoice)
        invoice_id2amount_to_pay = {}
        for invoice in invoices:
            if invoice.type == 'out':
                sign = -1
            else:
                sign = 1
            invoice_id2amount_to_pay[invoice.id] = sign * invoice.amount_to_pay

        lines = list(self.lines)
        line_offset = 0
        for index, line in enumerate(self.lines or []):
            if line.invoice and line.id:
                if line.invoice.id not in invoice_id2amount_to_pay:
                    continue
                amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
                if (amount_to_pay
                        and getattr(line, 'amount', None)
                        and (line.amount >= 0) == (amount_to_pay <= 0)):
                    if abs(line.amount) > abs(amount_to_pay):
                        new_line = Line()
                        for field_name, field in Line._fields.items():
                            if field_name == 'id':
                                continue
                            try:
                                setattr(new_line, field_name,
                                    getattr(line, field_name))
                            except AttributeError:
                                pass
                        new_line.amount = line.amount + amount_to_pay
                        new_line.invoice = None
                        line_offset += 1
                        lines.insert(index + line_offset, new_line)
                        invoice_id2amount_to_pay[line.invoice.id] = 0
                        line.amount = amount_to_pay.copy_sign(line.amount)
                    else:
                        invoice_id2amount_to_pay[line.invoice.id] = (
                            line.amount + amount_to_pay)
                else:
                    line.invoice = None
        self.lines = lines

    @fields.depends('journal')
    def on_change_with_validation(self, name=None):
        if self.journal:
            return self.journal.validation

    def get_to_reconcile(self, name=None):
        return bool(self.lines_to_reconcile)

    @property
    def lines_to_reconcile(self):
        lines = []
        for line in self.lines:
            if line.move:
                for move_line in line.move.lines:
                    if (move_line.account.reconcile
                            and not move_line.reconciliation):
                        lines.append(move_line)
        return lines

    def _group_key(self, line):
        key = (
            ('number', line.number or Unequal()),
            ('date', line.date),
            ('party', line.party),
            )
        return key

    def _get_grouped_line(self):
        "Return Line class for grouped lines"
        lines = self.origins or self.lines
        assert lines

        keys = [k[0] for k in self._group_key(lines[0])]

        class Line(namedtuple('Line', keys + ['lines'])):

            @property
            def amount(self):
                return sum((l.amount for l in self.lines))

            @property
            def descriptions(self):
                done = set()
                for line in self.lines:
                    if line.description and line.description not in done:
                        done.add(line.description)
                        yield line.description
        return Line

    @property
    def grouped_lines(self):
        if self.origins:
            lines = self.origins
        elif self.lines:
            lines = self.lines
        else:
            return
        Line = self._get_grouped_line()
        for key, lines in groupby(lines, key=self._group_key):
            yield Line(**dict(key + (('lines', list(lines)),)))

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree', 'visual', If(Eval('state') == 'cancelled', 'muted', '')),
            ]

    @classmethod
    def delete(cls, statements):
        # Cancel before delete
        cls.cancel(statements)
        for statement in statements:
            if statement.state != 'cancelled':
                raise AccessError(
                    gettext('account_statement.msg_statement_delete_cancel',
                        statement=statement.rec_name))
        super(Statement, cls).delete(statements)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, statements):
        pass

    def validate_balance(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        amount = (self.start_balance
            + sum(l.amount for l in self.lines))
        if amount != self.end_balance:
            lang = Lang.get()
            end_balance = lang.currency(
                self.end_balance, self.journal.currency)
            amount = lang.currency(amount, self.journal.currency)
            raise StatementValidateError(
                gettext('account_statement.msg_statement_wrong_end_balance',
                    statement=self.rec_name,
                    end_balance=end_balance,
                    amount=amount))

    def validate_amount(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        amount = sum(l.amount for l in self.lines)
        if amount != self.total_amount:
            lang = Lang.get()
            total_amount = lang.currency(
                self.total_amount, self.journal.currency)
            amount = lang.currency(amount, self.journal.currency)
            raise StatementValidateError(
                gettext('account_statement.msg_statement_wrong_total_amount',
                    statement=self.rec_name,
                    total_amount=total_amount,
                    amount=amount))

    def validate_number_of_lines(self):
        number = len(list(self.grouped_lines))
        if number > self.number_of_lines:
            raise StatementValidateError(
                gettext('account_statement'
                    '.msg_statement_wrong_number_of_lines_remove',
                    statement=self.rec_name,
                    n=number - self.number_of_lines))
        elif number < self.number_of_lines:
            raise StatementValidateError(
                gettext('account_statement'
                    '.msg_statement_wrong_number_of_lines_remove',
                    statement=self.rec_name,
                    n=self.number_of_lines - number))

    @classmethod
    @ModelView.button
    @Workflow.transition('validated')
    def validate_statement(cls, statements):
        pool = Pool()
        Line = pool.get('account.statement.line')
        Warning = pool.get('res.user.warning')
        paid_cancelled_invoice_lines = []
        for statement in statements:
            getattr(statement, 'validate_%s' % statement.validation)()
            paid_cancelled_invoice_lines.extend(l for l in statement.lines
                if l.invoice and l.invoice.state in {'cancelled', 'paid'})

        if paid_cancelled_invoice_lines:
            warning_key = Warning.format(
                'statement_paid_cancelled_invoice_lines',
                paid_cancelled_invoice_lines)
            if Warning.check(warning_key):
                raise StatementValidateWarning(warning_key,
                    gettext('account_statement'
                        '.msg_statement_invoice_paid_cancelled'))
            Line.write(paid_cancelled_invoice_lines, {
                    'related_to': None,
                    })

        cls.create_move(statements)

        cls.write(statements, {
                'state': 'validated',
                })
        common_lines = [l for l in Line.search([
                    ('statement.state', '=', 'draft'),
                    ('related_to.state', 'in', ['posted', 'paid'],
                        'account.invoice'),
                    ])
            if l.invoice.reconciled]
        if common_lines:
            warning_key = '_'.join(str(l.id) for l in common_lines)
            if Warning.check(warning_key):
                raise StatementValidateWarning(warning_key,
                    gettext('account_statement'
                        '.msg_statement_paid_invoice_draft'))
            Line.write(common_lines, {
                    'related_to': None,
                    })

    @classmethod
    def create_move(cls, statements):
        '''Create move for the statements and try to reconcile the lines.
        Returns the list of move, statement and lines
        '''
        pool = Pool()
        Line = pool.get('account.statement.line')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')

        moves = []
        for statement in statements:
            for key, lines in groupby(
                    statement.lines, key=statement._group_key):
                lines = list(lines)
                key = dict(key)
                move = statement._get_move(key)
                moves.append((move, statement, lines))

        Move.save([m for m, _, _ in moves])

        to_write = []
        for move, _, lines in moves:
            to_write.append(lines)
            to_write.append({
                    'move': move.id,
                    })
        if to_write:
            Line.write(*to_write)

        move_lines = []
        for move, statement, lines in moves:
            amount = 0
            amount_second_currency = 0
            for line in lines:
                move_line = line.get_move_line()
                move_line.move = move
                amount += move_line.debit - move_line.credit
                if move_line.amount_second_currency:
                    amount_second_currency += move_line.amount_second_currency
                move_lines.append((move_line, line))

            move_line = statement._get_move_line(
                amount, amount_second_currency, lines)
            move_line.move = move
            move_lines.append((move_line, None))

        MoveLine.save([l for l, _ in move_lines])

        Line.reconcile(move_lines)
        return moves

    def _get_move(self, key):
        'Return Move for the grouping key'
        pool = Pool()
        Move = pool.get('account.move')
        Period = pool.get('account.period')

        period_id = Period.find(self.company.id, date=key['date'])
        return Move(
            period=period_id,
            journal=self.journal.journal,
            date=key['date'],
            origin=self,
            company=self.company,
            description=str(key['number']),
            )

    def _get_move_line(self, amount, amount_second_currency, lines):
        'Return counterpart Move Line for the amount'
        pool = Pool()
        MoveLine = pool.get('account.move.line')

        if self.journal.currency != self.company.currency:
            second_currency = self.journal.currency
            amount_second_currency *= -1
        else:
            second_currency = None
            amount_second_currency = None

        descriptions = {l.description for l in lines}
        if len(descriptions) == 1:
            description, = descriptions
        else:
            description = ''

        return MoveLine(
            debit=abs(amount) if amount < 0 else 0,
            credit=abs(amount) if amount > 0 else 0,
            account=self.journal.account,
            second_currency=second_currency,
            amount_second_currency=amount_second_currency,
            description=description,
            )

    @classmethod
    @ModelView.button
    @Workflow.transition('posted')
    def post(cls, statements):
        pool = Pool()
        Lang = pool.get('ir.lang')
        StatementLine = pool.get('account.statement.line')
        for statement in statements:
            for origin in statement.origins:
                if origin.pending_amount:
                    lang = Lang.get()
                    amount = lang.currency(
                        origin.pending_amount, statement.journal.currency)
                    raise StatementPostError(
                        gettext('account_statement'
                            '.msg_statement_post_pending_amount',
                            statement=statement.rec_name,
                            amount=amount,
                            origin=origin.rec_name))
        # Write state to skip statement test on Move.post
        cls.write(statements, {'state': 'posted'})
        lines = [l for s in statements for l in s.lines]
        StatementLine.post_move(lines)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, statements):
        StatementLine = Pool().get('account.statement.line')

        lines = [l for s in statements for l in s.lines]
        StatementLine.delete_move(lines)

    @classmethod
    @ModelView.button_action('account_statement.act_reconcile')
    def reconcile(cls, statements):
        pass

    @classmethod
    def copy(cls, statements, default=None):
        default = default.copy() if default is not None else {}
        new_statements = []
        for origins, sub_statements in groupby(
                statements, key=lambda s: bool(s.origins)):
            sub_statements = list(sub_statements)
            sub_default = default.copy()
            if origins:
                sub_default.setdefault('lines')
            new_statements.extend(super().copy(
                    statements, default=sub_default))
        return new_statements
コード例 #27
0
ファイル: statement.py プロジェクト: tryton/account_statement
    class Mixin:
        __slots__ = ()
        statement = fields.Many2One(
            'account.statement', "Statement",
            required=True, ondelete='CASCADE', states=_states)
        statement_state = fields.Function(
            fields.Selection('get_statement_states', "Statement State"),
            'on_change_with_statement_state')
        company = fields.Function(
            fields.Many2One('company.company', "Company"),
            'on_change_with_company', searcher='search_company')
        number = fields.Char("Number")
        date = fields.Date(
            "Date", required=True, states=_states)
        amount = Monetary(
            "Amount", currency='currency', digits='currency', required=True,
            states=_states)
        currency = fields.Function(fields.Many2One(
                'currency.currency', "Currency"), 'on_change_with_currency')
        party = fields.Many2One(
            'party.party', "Party", states=_states,
            context={
                'company': Eval('company', -1),
                },
            depends={'company'})
        account = fields.Many2One(
            'account.account', "Account",
            domain=[
                ('company', '=', Eval('company', 0)),
                ('type', '!=', None),
                ('closed', '!=', True),
                ],
            context={
                'date': Eval('date'),
                },
            states=_states, depends={'date'})
        description = fields.Char("Description", states=_states)

        @classmethod
        def __setup__(cls):
            super().__setup__()
            cls.__access__.add('statement')

        @classmethod
        def get_statement_states(cls):
            pool = Pool()
            Statement = pool.get('account.statement')
            return Statement.fields_get(['state'])['state']['selection']

        @fields.depends('statement', '_parent_statement.state')
        def on_change_with_statement_state(self, name=None):
            if self.statement:
                return self.statement.state

        @fields.depends('statement', '_parent_statement.company')
        def on_change_with_company(self, name=None):
            if self.statement and self.statement.company:
                return self.statement.company.id

        @classmethod
        def search_company(cls, name, clause):
            return [('statement.' + clause[0],) + tuple(clause[1:])]

        @fields.depends('statement', '_parent_statement.journal')
        def on_change_with_currency(self, name=None):
            if self.statement and self.statement.journal:
                return self.statement.journal.currency.id
コード例 #28
0
class Action(ModelSQL, ModelView):
    'Customer Complaint Action'
    __name__ = 'sale.complaint.action'

    _states = {
        'readonly': ((Eval('complaint_state') != 'draft')
                     | Bool(Eval('result'))),
    }
    _line_states = {
        'invisible':
        ~Eval('_parent_complaint', {}).get('origin_model', 'sale.line').in_(
            ['sale.line', 'account.invoice.line']),
        'readonly':
        _states['readonly'],
    }

    complaint = fields.Many2One('sale.complaint',
                                'Complaint',
                                required=True,
                                ondelete='CASCADE',
                                states=_states)
    action = fields.Selection([
        ('sale_return', 'Create Sale Return'),
        ('credit_note', 'Create Credit Note'),
    ],
                              'Action',
                              states=_states)

    sale_lines = fields.One2Many(
        'sale.complaint.action-sale.line',
        'action',
        "Sale Lines",
        states={
            'invisible':
            Eval('_parent_complaint', {}).get('origin_model', 'sale.sale') !=
            'sale.sale',
            'readonly':
            _states['readonly'],
        },
        help='Leave empty for all lines.')

    invoice_lines = fields.One2Many(
        'sale.complaint.action-account.invoice.line',
        'action',
        "Invoice Lines",
        states={
            'invisible':
            Eval('_parent_complaint', {}).get(
                'origin_model', 'account.invoice.line') != 'account.invoice',
            'readonly':
            _states['readonly'],
        },
        help='Leave empty for all lines.')

    quantity = fields.Float("Quantity",
                            digits='unit',
                            states=_line_states,
                            help='Leave empty for the same quantity.')
    unit = fields.Function(
        fields.Many2One('product.uom', 'Unit', states=_line_states),
        'on_change_with_unit')
    unit_price = Monetary("Unit Price",
                          currency='currency',
                          digits=price_digits,
                          states=_line_states,
                          help='Leave empty for the same price.')

    amount = fields.Function(Monetary("Amount", 'currency', digits='currency'),
                             'on_change_with_amount')
    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')

    result = fields.Reference('Result', selection='get_result', readonly=True)

    complaint_state = fields.Function(
        fields.Selection('get_complaint_states', "Complaint State"),
        'on_change_with_complaint_state')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('complaint')

    @fields.depends('complaint', '_parent_complaint.origin_model',
                    '_parent_complaint.origin')
    def on_change_with_unit(self, name=None):
        if (self.complaint and self.complaint.origin_model
                in {'sale.line', 'account.invoice.line'}):
            return self.complaint.origin.unit.id

    @fields.depends('quantity', 'unit_price', 'currency', 'sale_lines',
                    'invoice_lines', 'complaint',
                    '_parent_complaint.origin_model',
                    '_parent_complaint.origin')
    def on_change_with_amount(self, name=None):
        if self.complaint:
            if self.complaint.origin_model in {
                    'sale.line', 'account.invoice.line'
            }:
                if self.quantity is not None:
                    quantity = self.quantity
                else:
                    quantity = self.complaint.origin.quantity
                if self.unit_price is not None:
                    unit_price = self.unit_price
                else:
                    unit_price = self.complaint.origin.unit_price
                amount = Decimal(str(quantity)) * unit_price
                if self.currency:
                    amount = self.currency.round(amount)
                return amount
            elif self.complaint.origin_model == 'sale.sale':
                if not self.sale_lines:
                    if self.complaint and self.complaint.origin:
                        return self.complaint.origin.untaxed_amount
                else:
                    return sum(
                        getattr(l, 'amount', None) or Decimal(0)
                        for l in self.sale_lines)
            elif self.complaint.origin_model == 'account.invoice':
                if not self.invoice_lines:
                    if self.complaint and self.complaint.origin:
                        return self.complaint.origin.untaxed_amount
                else:
                    return sum(
                        getattr(l, 'amount', None) or Decimal(0)
                        for l in self.invoice_lines)

    @fields.depends('complaint', '_parent_complaint.origin_model',
                    '_parent_complaint.origin')
    def on_change_with_currency(self, name=None):
        if (self.complaint and self.complaint.origin_model in {
                'sale.sale', 'sale.line', 'account.invoice',
                'account.invoice.line'
        }):
            return self.complaint.origin.currency.id

    @classmethod
    def get_complaint_states(cls):
        pool = Pool()
        Complaint = pool.get('sale.complaint')
        return Complaint.fields_get(['state'])['state']['selection']

    @fields.depends('complaint', '_parent_complaint.state')
    def on_change_with_complaint_state(self, name=None):
        if self.complaint:
            return self.complaint.state

    @classmethod
    def _get_result(cls):
        'Return list of Model names for result Reference'
        return ['sale.sale', 'account.invoice']

    @classmethod
    def get_result(cls):
        pool = Pool()
        Model = pool.get('ir.model')
        get_name = Model.get_name
        models = cls._get_result()
        return [(None, '')] + [(m, get_name(m)) for m in models]

    @classmethod
    def copy(cls, actions, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('result', None)
        return super().copy(actions, default=default)

    def do(self):
        return getattr(self, 'do_%s' % self.action)()

    def do_sale_return(self):
        pool = Pool()
        Sale = pool.get('sale.sale')
        Line = pool.get('sale.line')

        if isinstance(self.complaint.origin, (Sale, Line)):
            default = {}
            if isinstance(self.complaint.origin, Sale):
                sale = self.complaint.origin
                if self.sale_lines:
                    sale_lines = [l.line for l in self.sale_lines]
                    line2qty = {
                        l.line.id: l.get_quantity()
                        for l in self.sale_lines
                    }
                    line2price = {
                        l.line.id: l.get_unit_price()
                        for l in self.sale_lines
                    }
                    default['quantity'] = lambda o: line2qty.get(o['id'])
                    default['unit_price'] = lambda o: line2price.get(o['id'])
                else:
                    sale_lines = [l for l in sale.lines if l.type == 'line']
            elif isinstance(self.complaint.origin, Line):
                sale_line = self.complaint.origin
                sale = sale_line.sale
                sale_lines = [sale_line]
                if self.quantity is not None:
                    default['quantity'] = self.quantity
                if self.unit_price is not None:
                    default['unit_price'] = self.unit_price
            return_sale, = Sale.copy([sale], default={'lines': None})
            default['sale'] = return_sale.id
            Line.copy(sale_lines, default=default)
        else:
            return
        return_sale.origin = self.complaint
        for line in return_sale.lines:
            if line.type == 'line':
                line.quantity *= -1
        return_sale.lines = return_sale.lines  # Force saving
        return return_sale

    def do_credit_note(self):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        Line = pool.get('account.invoice.line')

        if isinstance(self.complaint.origin, (Invoice, Line)):
            line2qty = line2price = {}
            if isinstance(self.complaint.origin, Invoice):
                invoice = self.complaint.origin
                if self.invoice_lines:
                    invoice_lines = [l.line for l in self.invoice_lines]
                    line2qty = {
                        l.line: l.quantity
                        for l in self.invoice_lines if l.quantity is not None
                    }
                    line2price = {
                        l.line: l.unit_price
                        for l in self.invoice_lines if l.unit_price is not None
                    }
                else:
                    invoice_lines = [
                        l for l in invoice.lines if l.type == 'line'
                    ]
            elif isinstance(self.complaint.origin, Line):
                invoice_line = self.complaint.origin
                invoice = invoice_line.invoice
                invoice_lines = [invoice_line]
                if self.quantity is not None:
                    line2qty = {invoice_line: self.quantity}
                if self.unit_price is not None:
                    line2price = {invoice_line: self.unit_price}
            with Transaction().set_context(_account_invoice_correction=True):
                credit_note, = Invoice.copy([invoice],
                                            default={
                                                'lines': [],
                                                'taxes': [],
                                            })
                # Copy each line one by one to get negative and positive lines
                # following each other
                for invoice_line in invoice_lines:
                    qty = line2qty.get(invoice_line, invoice_line.quantity)
                    unit_price = invoice_line.unit_price - line2price.get(
                        invoice_line, invoice_line.unit_price)
                    Line.copy(
                        [invoice_line],
                        default={
                            'invoice': credit_note.id,
                            'quantity': -qty,
                            'origin': str(self.complaint),
                        })
                    credit_line, = Line.copy(
                        [invoice_line],
                        default={
                            'invoice': credit_note.id,
                            'quantity': qty,
                            'unit_price': unit_price,
                            'origin': str(self.complaint),
                        })
            credit_note.update_taxes()
        else:
            return
        return credit_note

    @classmethod
    def delete(cls, actions):
        for action in actions:
            if action.result:
                raise AccessError(
                    gettext('sale_complaint.msg_action_delete_result',
                            action=action.rec_name))
        super(Action, cls).delete(actions)
コード例 #29
0
class Party(metaclass=PoolMeta):
    __name__ = 'party.party'

    deposit = fields.Function(Monetary("Deposit",
                                       currency='currency',
                                       digits='currency'),
                              'get_deposit',
                              searcher='search_deposit')

    @classmethod
    def get_deposit(cls, parties, name):
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Account = pool.get('account.account')
        AccountType = pool.get('account.account.type')
        User = pool.get('res.user')
        cursor = Transaction().connection.cursor()

        line = MoveLine.__table__()
        account = Account.__table__()
        account_type = AccountType.__table__()

        values = {p.id: Decimal(0) for p in parties}

        user = User(Transaction().user)
        if not user.company:
            return values
        currency = user.company.currency

        line_clause, _ = MoveLine.query_get(line)

        for sub_parties in grouped_slice(parties):
            party_clause = reduce_ids(line.party, [p.id for p in sub_parties])
            cursor.execute(
                *line.join(account, condition=account.id == line.account).join(
                    account_type, condition=account.type == account_type.id).
                select(
                    line.party,
                    # Use credit - debit to positive deposit amount
                    Sum(Coalesce(line.credit, 0) - Coalesce(line.debit, 0)),
                    where=account_type.deposit
                    & party_clause
                    & (line.reconciliation == Null)
                    & (account.company == user.company.id)
                    & line_clause,
                    group_by=line.party))
            for party_id, value in cursor:
                # SQLite uses float for SUM
                if not isinstance(value, Decimal):
                    value = currency.round(Decimal(str(value)))
                values[party_id] = value
        return values

    @classmethod
    def search_deposit(cls, name, clause):
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Account = pool.get('account.account')
        AccountType = pool.get('account.account.type')
        User = pool.get('res.user')

        line = MoveLine.__table__()
        account = Account.__table__()
        account_type = AccountType.__table__()

        user = User(Transaction().user)
        if not user.company:
            return []

        line_clause, _ = MoveLine.query_get(line)
        Operator = fields.SQL_OPERATORS[clause[1]]

        query = (line.join(account, condition=account.id == line.account).join(
            account_type, condition=account.type == account_type.id).select(
                line.party,
                where=account.active
                & account_type.deposit
                & (line.party != Null)
                & (line.reconciliation == Null)
                & (account.company == user.company.id)
                & line_clause,
                group_by=line.party,
                having=Operator(
                    Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)),
                    Decimal(clause[2] or 0))))
        return [('id', 'in', query)]

    def get_deposit_balance(self, deposit_account):
        'Return the deposit account balance (debit - credit) for the party'
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        transaction = Transaction()
        cursor = transaction.connection.cursor()

        line = MoveLine.__table__()
        assert deposit_account.type.deposit

        where = ((line.account == deposit_account.id)
                 & (line.party == self.id)
                 & (line.reconciliation == Null))
        if transaction.database.has_select_for():
            cursor.execute(*line.select(
                Literal(1), where=where, for_=For('UPDATE', nowait=True)))
        else:
            MoveLine.lock()
        cursor.execute(*line.select(Sum(
            Coalesce(line.debit, 0) - Coalesce(line.credit, 0)),
                                    where=where))
        amount, = cursor.fetchone()
        if amount and not isinstance(amount, Decimal):
            currency = deposit_account.company.currency
            amount = currency.round(Decimal(str(amount)))
        return amount or Decimal(0)

    def check_deposit(self, deposit_account, sign=1):
        '''Check if the deposit account balance (debit - credit) has the same
        sign for the party'''
        assert sign in (1, -1)
        amount = self.get_deposit_balance(deposit_account)
        return not amount or ((amount < 0) == (sign < 0))
コード例 #30
0
class SaleOpportunityReportMixin:
    __slots__ = ()
    number = fields.Integer('Number')
    converted = fields.Integer('Converted')
    conversion_rate = fields.Function(
        fields.Float('Conversion Rate', digits=(1, 4)), 'get_conversion_rate')
    won = fields.Integer('Won')
    winning_rate = fields.Function(fields.Float('Winning Rate', digits=(1, 4)),
                                   'get_winning_rate')
    lost = fields.Integer('Lost')
    company = fields.Many2One('company.company', 'Company')
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'), 'get_currency')
    amount = Monetary("Amount", currency='currency', digits='currency')
    converted_amount = Monetary("Converted Amount",
                                currency='currency',
                                digits='currency')
    conversion_amount_rate = fields.Function(
        fields.Float('Conversion Amount Rate', digits=(1, 4)),
        'get_conversion_amount_rate')
    won_amount = Monetary("Won Amount", currency='currency', digits='currency')
    winning_amount_rate = fields.Function(
        fields.Float('Winning Amount Rate', digits=(1, 4)),
        'get_winning_amount_rate')

    @staticmethod
    def _converted_state():
        return ['converted', 'won']

    @staticmethod
    def _won_state():
        return ['won']

    @staticmethod
    def _lost_state():
        return ['lost']

    def get_conversion_rate(self, name):
        if self.number:
            digits = getattr(self.__class__, name).digits[1]
            return round(float(self.converted) / self.number, digits)
        else:
            return 0.0

    def get_winning_rate(self, name):
        if self.number:
            digits = getattr(self.__class__, name).digits[1]
            return round(float(self.won) / self.number, digits)
        else:
            return 0.0

    def get_currency(self, name):
        return self.company.currency.id

    def get_conversion_amount_rate(self, name):
        if self.amount:
            digits = getattr(self.__class__, name).digits[1]
            return round(
                float(self.converted_amount) / float(self.amount), digits)
        else:
            return 0.0

    def get_winning_amount_rate(self, name):
        if self.amount:
            digits = getattr(self.__class__, name).digits[1]
            return round(float(self.won_amount) / float(self.amount), digits)
        else:
            return 0.0

    @classmethod
    def table_query(cls):
        Opportunity = Pool().get('sale.opportunity')
        opportunity = Opportunity.__table__()
        return opportunity.select(
            Max(opportunity.create_uid).as_('create_uid'),
            Max(opportunity.create_date).as_('create_date'),
            Max(opportunity.write_uid).as_('write_uid'),
            Max(opportunity.write_date).as_('write_date'), opportunity.company,
            Count(Literal(1)).as_('number'),
            Sum(
                Case((opportunity.state.in_(
                    cls._converted_state()), Literal(1)),
                     else_=Literal(0))).as_('converted'),
            Sum(
                Case((opportunity.state.in_(cls._won_state()), Literal(1)),
                     else_=Literal(0))).as_('won'),
            Sum(
                Case((opportunity.state.in_(cls._lost_state()), Literal(1)),
                     else_=Literal(0))).as_('lost'),
            Sum(opportunity.amount).as_('amount'),
            Sum(
                Case((opportunity.state.in_(
                    cls._converted_state()), opportunity.amount),
                     else_=Literal(0))).as_('converted_amount'),
            Sum(
                Case((opportunity.state.in_(
                    cls._won_state()), opportunity.amount),
                     else_=Literal(0))).as_('won_amount'))