class InvoiceEdi(ModelSQL, ModelView):
    'Account Invoice Edi Reception Header'
    __name__ = 'invoice.edi'

    company = fields.Many2One('company.company', 'Company', readonly=True)
    number = fields.Char('Number', readonly=True)
    invoice = fields.Many2One('account.invoice', 'Invoice')
    type_ = fields.Selection([('380', 'Invoice'), ('381', 'Credit'),
                              ('383', 'Charge Note')],
                             'Document Type',
                             readonly=True)
    function_ = fields.Selection([(None, ''), ('9', 'Original'),
                                  ('31', 'Copy')],
                                 'Message Function',
                                 readonly=True)
    invoice_date = fields.Date('Invoice Date', readonly=True)
    service_date = fields.Date('Service Date', readonly=True)
    start_period_date = fields.Date('Start Period', readonly=True)
    end_period_date = fields.Date('End Period', readonly=True)
    payment_type_value = fields.Selection([(None, ''), ('42', 'Account Bank'),
                                           ('10', 'cash'), ('20', 'check'),
                                           ('60', 'Bank Note'),
                                           ('14E', 'Bank Draft'),
                                           ('30', 'Credit Transfer')],
                                          'Payment Type Value',
                                          readonly=True)
    payment_type = fields.Many2One('account.payment.type',
                                   'Payment Type',
                                   readonly=True)
    factoring = fields.Boolean('Factoring', readonly=True)
    currency_code = fields.Char('Currency Code', readonly=True)
    currency = fields.Many2One('currency.currency', 'Currency', readonly=True)
    maturity_dates = fields.One2Many('invoice.edi.maturity_date',
                                     'invoice_edi',
                                     'Maturity Dates',
                                     readonly=True)
    discounts = fields.One2Many('invoice.edi.discount',
                                'invoice_edi',
                                'Discounts',
                                readonly=True)
    net_amount = fields.Numeric('Net Amount', digits=(16, 2), readonly=True)
    gross_amount = fields.Numeric('Gross Amount',
                                  digits=(16, 2),
                                  readonly=True)
    base_amount = fields.Numeric('Base Amount', digits=(16, 2), readonly=True)
    total_amount = fields.Numeric('Total Amount',
                                  digits=(16, 2),
                                  readonly=True)
    tax_amount = fields.Numeric('Tax Amount', digits=(16, 2), readonly=True)
    discount_amount = fields.Numeric('Discount Amount',
                                     digits=(16, 2),
                                     readonly=True)
    charge_amount = fields.Numeric('Charge Amount',
                                   digits=(16, 2),
                                   readonly=True)
    taxes = fields.One2Many('invoice.edi.tax',
                            'edi_invoice',
                            'Taxes',
                            readonly=True)
    lines = fields.One2Many('invoice.edi.line', 'edi_invoice', 'Lines')
    suppliers = fields.One2Many('invoice.edi.supplier',
                                'edi_invoice',
                                'Supplier',
                                readonly=True)
    references = fields.One2Many('invoice.edi.reference',
                                 'edi_invoice',
                                 'References',
                                 readonly=True)
    state = fields.Function(fields.Selection([('draft', 'Draft'),
                                              ('confirmed', 'Confirmed')],
                                             'State'),
                            'get_state',
                            searcher='search_state')
    differences_state = fields.Function(
        fields.Selection([(None, ''), ('ok', 'Ok'),
                          ('difference', 'Difference')], 'Difference State'),
        'get_differences_state')
    difference_amount = fields.Function(
        fields.Numeric('Differences', digits=(16, 2)), 'get_difference_amount')
    party = fields.Function(fields.Many2One('party.party', 'Invoice Party'),
                            'get_party',
                            searcher='search_party')
    manual_party = fields.Many2One('party.party', 'Manual Party')
    comment = fields.Text('Comment')

    @classmethod
    def __setup__(cls):
        super(InvoiceEdi, cls).__setup__()
        cls._order = [
            ('invoice_date', 'DESC'),
            ('id', 'DESC'),
        ]
        cls._buttons.update({'create_invoices': {}, 'search_references': {}})

    @staticmethod
    def default_state():
        return 'draft'

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

    def get_state(self, name):
        if self.invoice:
            return 'confirmed'
        return 'draft'

    @classmethod
    def search_state(cls, name, clause):
        if clause[-1] == 'draft':
            return [('invoice', clause[1], None)]
        return [('invoice', '!=', None)]

    def get_differences_state(self, name):
        if not self.invoice:
            return
        if self.invoice.total_amount != self.total_amount:
            return 'difference'
        return 'ok'

    def get_difference_amount(self, name):
        if not self.invoice:
            return
        return self.total_amount - self.invoice.total_amount

    @classmethod
    def search_party(cls, name, clause):
        return [
            'OR', ('manual_party', ) + tuple(clause[1:]),
            [('suppliers.type_', '=', 'NADSU'),
             ('suppliers.party', ) + tuple(clause[1:])]
        ]

    def get_party(self, name):
        if self.manual_party:
            return self.manual_party.id
        for s in self.suppliers:
            if s.type_ == 'NADSU':
                return s.party and s.party.id

    def read_INV(self, message):
        self.number = message.pop(0) if message else ''
        self.type_ = message.pop(0) if message else ''
        self.function_ = '9'
        if message:
            self.function_ = message.pop(0)

    def read_TXT(self, message):
        self.comment = message.pop(0) if message else ''

    def read_DTM(self, message):
        self.invoice_date = to_date(message.pop(0)[0:8]) if message else None
        if message:
            self.service_date = to_date(message.pop(0)[0:8])
        if message:
            period = message.pop(0)
            self.start_period_date = to_date(period[0:8])
            self.end_period_date = to_date(period[8:])

    def read_PAI(self, message):
        payment_type = message.pop(0) if message else ''
        self.payment_type_value = payment_type
        factoring = False
        if message:
            factoring = message.pop(0)

        self.factoring = factoring

    def read_RFF(self, message):
        REF = Pool().get('invoice.edi.reference')
        ref = REF()
        ref.type_ = message.pop(0) if message else ''
        ref.value = message.pop(0) if message else ''
        if message:
            ref.line_number = message.pop(0)
        ref.search_reference()
        if not getattr(self, 'references', False):
            self.references = []
        self.references += (ref, )

    def read_CUX(self, message):
        message.pop(0)
        self.currency_code = message.pop(0) if message else ''

    def read_PAT(self, message):
        MaturityDate = Pool().get('invoice.edi.maturity_date')
        line = MaturityDate()
        line.type_ = message.pop(0) if message else ''
        line.maturity_date = to_date(message.pop(0)) if message else None
        if message:
            line.amount = to_decimal(message.pop(0))
        if not getattr(self, 'maturity_dates', False):
            self.maturity_dates = []
        self.maturity_dates += (line, )

    def read_ALC(self, message):
        Discount = Pool().get('invoice.edi.discount')
        discount = Discount()
        discount.type_ = message.pop(0) if message else ''
        sequence = message.pop(0) if message else ''
        discount.sequence = int(sequence) or None
        discount.discount = message.pop(0) if message else ''
        discount.percent = to_decimal(
            message.pop(0)) if message else Decimal(0)
        discount.amount = to_decimal(message.pop(0)) if message else Decimal(0)
        if not getattr(self, 'discounts', False):
            self.discounts = []
        self.discounts += (discount, )

    def read_MOARES(self, message):
        self.net_amount = to_decimal(message.pop(0)) if message else Decimal(0)
        self.gross_amount = to_decimal(
            message.pop(0)) if message else Decimal(0)
        self.base_amount = to_decimal(
            message.pop(0)) if message else Decimal(0)
        self.total_amount = to_decimal(
            message.pop(0)) if message else Decimal(0)
        self.tax_amount = to_decimal(message.pop(0)) if message else Decimal(0)
        if message:
            self.discount_amount = to_decimal(message.pop(0))
        if message:
            self.charge_amount = to_decimal(message.pop(0))

    def read_TAXRES(self, message):
        Tax = Pool().get('invoice.edi.tax')
        tax = Tax()
        tax.type_ = message.pop(0)
        tax.percent = to_decimal(message.pop(0)) if message else Decimal(0)
        tax.tax_amount = to_decimal(message.pop(0)) if message else Decimal(0)
        print(message)
        if message:
            tax.base_amount = to_decimal(message.pop(0))
        tax.search_tax()
        if not getattr(self, 'taxes', False):
            self.taxes = []
        self.taxes += (tax, )

    def read_CNTRES(self, message):
        pass

    @classmethod
    def import_edi_file(cls, data):
        pool = Pool()
        Invoice = pool.get('invoice.edi')
        InvoiceLine = pool.get('invoice.edi.line')
        SupplierEdi = pool.get('invoice.edi.supplier')
        Configuration = pool.get('invoice.edi.configuration')

        config = Configuration(1)
        separator = config.separator

        invoice = None
        invoice_line = None
        document_type = data.pop(0)
        if document_type == 'INVOIC_D_93A_UN_EAN007':
            return
        for line in data:
            line = line.replace('\n', '').replace('\r', '')
            line = line.split(separator)
            msg_id = line.pop(0)
            if msg_id == 'INV':
                invoice = Invoice()
                invoice.read_INV(line)
            elif msg_id == 'LIN':
                # if invoice_line:
                #     invoice_line.search_related(invoice)
                invoice_line = InvoiceLine()
                invoice_line.read_LIN(line)
                if not getattr(invoice, 'lines', False):
                    invoice.lines = []
                invoice.lines += (invoice_line, )
            elif 'LIN' in msg_id:
                if hasattr(invoice_line, 'read_%s' % msg_id):
                    getattr(invoice_line, 'read_%s' % msg_id)(line)
            elif msg_id in [x[0] for x in SUPPLIER_TYPE]:
                supplier = SupplierEdi()
                if hasattr(supplier, 'read_%s' % msg_id):
                    getattr(supplier, 'read_%s' % msg_id)(line)
                supplier.search_party()
                if not getattr(invoice, 'suppliers', False):
                    invoice.suppliers = []
                invoice.suppliers += (supplier, )
            elif 'NAD' in msg_id:
                continue
            else:
                if hasattr(invoice, 'read_%s' % msg_id):
                    getattr(invoice, 'read_%s' % msg_id)(line)

        # invoice_line.search_related(invoice)
        return invoice

    def add_attachment(self, attachment, filename=None):
        pool = Pool()
        Attachment = pool.get('ir.attachment')
        if not filename:
            filename = datetime.now().strftime("%y/%m/%d %H:%M:%S")
        attach = Attachment(name=filename,
                            type='data',
                            data=attachment.encode('utf8'),
                            resource=self)
        attach.save()

    @classmethod
    def import_edi_files(cls, invoices=None):
        pool = Pool()
        Configuration = pool.get('invoice.edi.configuration')
        configuration = Configuration(1)
        source_path = os.path.abspath(configuration.edi_files_path
                                      or DEFAULT_FILES_LOCATION)
        files = [
            os.path.join(source_path, fp) for fp in os.listdir(source_path)
            if os.path.isfile(os.path.join(source_path, fp))
        ]
        files_to_delete = []
        to_save = []
        attachments = dict()
        for fname in files:
            if fname[-4:].lower() not in KNOWN_EXTENSIONS:
                continue
            with codecs.open(fname, 'rb', 'latin-1') as fp:
                data = fp.readlines()
                invoice = cls.import_edi_file(data)

            basename = os.path.basename(fname)
            if invoice:
                attachments[invoice] = ("\n".join(data), basename)
                to_save.append(invoice)
                files_to_delete.append(fname)

        if to_save:
            cls.save(to_save)

        with Transaction().set_user(0, set_context=True):
            for invoice, (data, basename) in attachments.items():
                invoice.add_attachment(data, basename)

        if files_to_delete:
            for file in files_to_delete:
                os.remove(file)

        cls.search_references(to_save)

    def get_invoice(self):
        pool = Pool()
        Invoice = pool.get('account.invoice')

        invoice = Invoice()
        invoice.currency = Invoice.default_currency()
        invoice.company = self.company
        invoice.party = self.party
        invoice.on_change_party()
        invoice.reference = self.number
        invoice.invoice_date = self.invoice_date
        invoice.type = 'in'
        invoice.on_change_type()
        invoice.account = invoice.on_change_with_account()
        invoice.state = 'draft'
        return invoice

    def get_discount_invoice_line(self, description, amount, taxes=None):
        Config = Pool().get('account.configuration')
        config = Config(1)
        Line = Pool().get('account.invoice.line')
        line = Line()
        line.invoice_type = 'in'
        line.party = self.party
        line.type = 'line'
        line.description = description
        line.quantity = 1
        line.unit_price = amount
        line.gross_unit_price = amount
        line.account = config.default_product_account_expense
        line.taxes = taxes
        return line

    @classmethod
    @ModelView.button
    def search_references(cls, edi_invoices):
        for edi_invoice in edi_invoices:
            if edi_invoice.invoice:
                continue
            invoice = edi_invoice.get_invoice()
            invoice.lines = []
            for eline in edi_invoice.lines:
                eline.search_related(edi_invoice)
                eline.save()

    @classmethod
    @ModelView.button
    def create_invoices(cls, edi_invoices):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        invoices = []
        to_save = []
        for edi_invoice in edi_invoices:
            if edi_invoice.invoice:
                continue
            invoice = edi_invoice.get_invoice()
            invoice.lines = []
            for eline in edi_invoice.lines:
                line = eline.get_line()
                invoice.lines += (line, )
            invoice.on_change_lines()
            invoice.on_change_lines()
            invoice.on_change_type()
            invoice.use_edi = True
            edi_invoice.invoice = invoice
            invoices.append(invoice)
            to_save.append(edi_invoice)

        Invoice.save(invoices)
        Invoice.validate(invoices)
        Invoice.draft(invoices)
        cls.save(to_save)
Exemple #2
0
class BOMInput(ModelSQL, ModelView):
    "Bill of Material Input"
    __name__ = 'production.bom.input'

    bom = fields.Many2One('production.bom',
                          'BOM',
                          required=True,
                          select=1,
                          ondelete='CASCADE')
    product = fields.Many2One('product.product', 'Product', required=True)
    uom_category = fields.Function(
        fields.Many2One('product.uom.category', 'Uom Category'),
        'on_change_with_uom_category')
    uom = fields.Many2One('product.uom',
                          'Uom',
                          required=True,
                          domain=[
                              ('category', '=', Eval('uom_category')),
                          ])
    quantity = fields.Float('Quantity',
                            digits='uom',
                            required=True,
                            domain=[
                                'OR',
                                ('quantity', '>=', 0),
                                ('quantity', '=', None),
                            ])

    @classmethod
    def __setup__(cls):
        super(BOMInput, cls).__setup__()
        cls.product.domain = [('type', 'in', cls.get_product_types())]
        cls.__access__.add('bom')

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

        # Migration from 6.0: remove unique constraint
        table_h.drop_constraint('product_bom_uniq')

    @classmethod
    def get_product_types(cls):
        return ['goods', 'assets']

    @fields.depends('product', 'uom')
    def on_change_product(self):
        if self.product:
            category = self.product.default_uom.category
            if not self.uom or self.uom.category != category:
                self.uom = self.product.default_uom
        else:
            self.uom = None

    @fields.depends('product')
    def on_change_with_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom.category.id

    def get_rec_name(self, name):
        return self.product.rec_name

    @classmethod
    def search_rec_name(cls, name, clause):
        return [('product.rec_name', ) + tuple(clause[1:])]

    @classmethod
    def validate(cls, boms):
        super(BOMInput, cls).validate(boms)
        for bom in boms:
            bom.check_bom_recursion()

    def check_bom_recursion(self):
        '''
        Check BOM recursion
        '''
        self.product.check_bom_recursion()

    def compute_quantity(self, factor):
        return self.uom.ceil(self.quantity * factor)
Exemple #3
0
class SaleOpportunity(Workflow, ModelSQL, ModelView, AttachmentCopyMixin,
                      NoteCopyMixin):
    'Sale Opportunity'
    __name__ = "sale.opportunity"
    _history = True
    _rec_name = 'number'

    _states_start = {
        'readonly': Eval('state') != 'lead',
    }
    _states_stop = {
        'readonly':
        Eval('state').in_(['converted', 'won', 'lost', 'cancelled']),
    }

    number = fields.Char('Number', readonly=True, required=True, select=True)
    reference = fields.Char('Reference', select=True)
    party = fields.Many2One(
        'party.party',
        "Party",
        select=True,
        states={
            'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']),
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
        },
        context={
            'company': Eval('company', -1),
        },
        depends={'company'})
    contact = fields.Many2One('party.contact_mechanism',
                              "Contact",
                              context={
                                  'company': Eval('company', -1),
                              },
                              search_context={
                                  'related_party': Eval('party'),
                              },
                              depends=['party', 'company'])
    address = fields.Many2One('party.address',
                              'Address',
                              domain=[('party', '=', Eval('party'))],
                              select=True,
                              states=_states_stop)
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        select=True,
        states={
            'readonly': _states_stop['readonly'] | Eval('party', True),
        },
        domain=[
            ('id', If(In('company', Eval('context', {})), '=',
                      '!='), Get(Eval('context', {}), 'company', 0)),
        ])
    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')
    amount = Monetary("Amount",
                      currency='currency',
                      digits='currency',
                      states=_states_stop,
                      help='Estimated revenue amount.')
    payment_term = fields.Many2One('account.invoice.payment_term',
                                   'Payment Term',
                                   states={
                                       'readonly':
                                       In(Eval('state'),
                                          ['converted', 'lost', 'cancelled']),
                                   })
    employee = fields.Many2One(
        'company.employee',
        'Employee',
        states={
            'readonly': _states_stop['readonly'],
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
        },
        domain=[('company', '=', Eval('company'))])
    start_date = fields.Date('Start Date',
                             required=True,
                             select=True,
                             states=_states_start)
    end_date = fields.Date('End Date', select=True, states=_states_stop)
    description = fields.Char('Description', states=_states_stop)
    comment = fields.Text('Comment', states=_states_stop)
    lines = fields.One2Many('sale.opportunity.line',
                            'opportunity',
                            'Lines',
                            states=_states_stop)
    conversion_probability = fields.Float(
        'Conversion Probability',
        digits=(1, 4),
        required=True,
        domain=[
            ('conversion_probability', '>=', 0),
            ('conversion_probability', '<=', 1),
        ],
        states={
            'readonly':
            ~Eval('state').in_(['opportunity', 'lead', 'converted']),
        },
        help="Percentage between 0 and 100.")
    lost_reason = fields.Text('Reason for loss',
                              states={
                                  'invisible': Eval('state') != 'lost',
                              })
    sales = fields.One2Many('sale.sale', 'origin', 'Sales')

    converted_by = employee_field(
        "Converted By", states=['converted', 'won', 'lost', 'cancelled'])
    state = fields.Selection([
        ('lead', "Lead"),
        ('opportunity', "Opportunity"),
        ('converted', "Converted"),
        ('won', "Won"),
        ('lost', "Lost"),
        ('cancelled', "Cancelled"),
    ],
                             "State",
                             required=True,
                             select=True,
                             sort=False,
                             readonly=True)

    del _states_start
    del _states_stop

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        Sale = pool.get('sale.sale')
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        update = transaction.connection.cursor()
        sql_table = cls.__table__()
        sale = Sale.__table__()

        table = cls.__table_handler__(module_name)
        number_exists = table.column_exist('number')

        # Migration from 3.8: rename reference into number
        if table.column_exist('reference') and not number_exists:
            table.column_rename('reference', 'number')
            number_exists = True

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

        # Migration from 3.4: replace sale by origin
        if table.column_exist('sale'):
            cursor.execute(*sql_table.select(
                sql_table.id, sql_table.sale, where=sql_table.sale != Null))
            for id_, sale_id in cursor:
                update.execute(
                    *sale.update(columns=[sale.origin],
                                 values=['%s,%s' % (cls.__name__, id_)],
                                 where=sale.id == sale_id))
            table.drop_column('sale')

        # Migration from 4.0: change probability into conversion probability
        if table.column_exist('probability'):
            cursor.execute(
                *sql_table.update([sql_table.conversion_probability],
                                  [sql_table.probability / 100.0]))
            table.drop_constraint('check_percentage')
            table.drop_column('probability')

        # Migration from 4.2: make employee not required
        table.not_null_action('employee', action='remove')

        # Migration from 5.0: drop required on description
        table.not_null_action('description', action='remove')

    @classmethod
    def __setup__(cls):
        super(SaleOpportunity, cls).__setup__()
        cls._order.insert(0, ('start_date', 'DESC'))
        cls._transitions |= set((
            ('lead', 'opportunity'),
            ('lead', 'lost'),
            ('lead', 'cancelled'),
            ('lead', 'converted'),
            ('opportunity', 'converted'),
            ('opportunity', 'lead'),
            ('opportunity', 'lost'),
            ('opportunity', 'cancelled'),
            ('converted', 'won'),
            ('converted', 'lost'),
            ('won', 'converted'),
            ('lost', 'converted'),
            ('lost', 'lead'),
            ('cancelled', 'lead'),
        ))
        cls._buttons.update({
            'lead': {
                'invisible':
                ~Eval('state').in_(['cancelled', 'lost', 'opportunity']),
                'icon':
                If(
                    Eval('state').in_(['cancelled', 'lost']), 'tryton-undo',
                    'tryton-back'),
                'depends': ['state'],
            },
            'opportunity': {
                'pre_validate': [
                    If(~Eval('party'), ('party', '!=', None), ()),
                    If(~Eval('employee'), ('employee', '!=', None), ()),
                ],
                'invisible':
                ~Eval('state').in_(['lead']),
                'depends': ['state'],
            },
            'convert': {
                'invisible': ~Eval('state').in_(['opportunity']),
                'depends': ['state'],
            },
            'lost': {
                'invisible': ~Eval('state').in_(['lead', 'opportunity']),
                'depends': ['state'],
            },
            'cancel': {
                'invisible': ~Eval('state').in_(['lead', 'opportunity']),
                'depends': ['state'],
            },
        })

    @staticmethod
    def default_state():
        return 'lead'

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

    @staticmethod
    def default_conversion_probability():
        return 0.5

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

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

    @classmethod
    def default_payment_term(cls):
        PaymentTerm = Pool().get('account.invoice.payment_term')
        payment_terms = PaymentTerm.search(cls.payment_term.domain)
        if len(payment_terms) == 1:
            return payment_terms[0].id

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

    @classmethod
    def get_resources_to_copy(cls, name):
        return {
            'sale.sale',
        }

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

        config = Config(1)
        vlist = [x.copy() for x in vlist]
        default_company = cls.default_company()
        for vals in vlist:
            if vals.get('number') is None:
                vals['number'] = config.get_multivalue(
                    'sale_opportunity_sequence',
                    company=vals.get('company', default_company)).get()
        return super(SaleOpportunity, cls).create(vlist)

    @classmethod
    def copy(cls, opportunities, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('number', None)
        default.setdefault('sales', None)
        default.setdefault('converted_by')
        return super(SaleOpportunity, cls).copy(opportunities, default=default)

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

    @fields.depends('party')
    def on_change_party(self):
        if self.party and self.party.customer_payment_term:
            self.payment_term = self.party.customer_payment_term
        else:
            self.payment_term = self.default_payment_term()

    def _get_sale_opportunity(self):
        '''
        Return sale for an opportunity
        '''
        Sale = Pool().get('sale.sale')
        return Sale(
            description=self.description,
            party=self.party,
            contact=self.contact,
            payment_term=self.payment_term,
            company=self.company,
            invoice_address=self.address,
            shipment_address=self.address,
            currency=self.company.currency,
            comment=self.comment,
            sale_date=None,
            origin=self,
            warehouse=Sale.default_warehouse(),
        )

    def create_sale(self):
        '''
        Create a sale for the opportunity and return the sale
        '''
        sale = self._get_sale_opportunity()
        sale_lines = []
        for line in self.lines:
            sale_lines.append(line.get_sale_line(sale))
        sale.lines = sale_lines
        return sale

    @classmethod
    def delete(cls, opportunities):
        # Cancel before delete
        cls.cancel(opportunities)
        for opportunity in opportunities:
            if opportunity.state != 'cancelled':
                raise AccessError(
                    gettext('sale_opportunity.msg_opportunity_delete_cancel',
                            opportunity=opportunity.rec_name))
        super(SaleOpportunity, cls).delete(opportunities)

    @classmethod
    @ModelView.button
    @Workflow.transition('lead')
    def lead(cls, opportunities):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('opportunity')
    def opportunity(cls, opportunities):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('converted')
    @set_employee('converted_by')
    def convert(cls, opportunities):
        pool = Pool()
        Sale = pool.get('sale.sale')
        sales = [o.create_sale() for o in opportunities if not o.sales]
        Sale.save(sales)
        for sale in sales:
            sale.origin.copy_resources_to(sale)

    @property
    def is_forecast(self):
        pool = Pool()
        Date = pool.get('ir.date')
        with Transaction().set_context(company=self.company.id):
            today = Date.today()
        return self.end_date or datetime.date.max > today

    @classmethod
    @Workflow.transition('won')
    def won(cls, opportunities):
        pool = Pool()
        Date = pool.get('ir.date')
        for company, c_opportunities in groupby(opportunities,
                                                key=lambda o: o.company):
            with Transaction().set_context(company=company.id):
                today = Date.today()
            cls.write([o for o in c_opportunities if o.is_forecast], {
                'end_date': today,
                'state': 'won',
            })

    @classmethod
    @ModelView.button
    @Workflow.transition('lost')
    def lost(cls, opportunities):
        pool = Pool()
        Date = pool.get('ir.date')
        for company, c_opportunities in groupby(opportunities,
                                                key=lambda o: o.company):
            with Transaction().set_context(company=company.id):
                today = Date.today()
            cls.write([o for o in c_opportunities if o.is_forecast], {
                'end_date': today,
                'state': 'lost',
            })

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, opportunities):
        pool = Pool()
        Date = pool.get('ir.date')
        for company, c_opportunities in groupby(opportunities,
                                                key=lambda o: o.company):
            with Transaction().set_context(company=company.id):
                today = Date.today()
            cls.write([o for o in c_opportunities if o.is_forecast], {
                'end_date': today,
                'state': 'cancelled',
            })

    @staticmethod
    def _sale_won_states():
        return ['confirmed', 'processing', 'done']

    @staticmethod
    def _sale_lost_states():
        return ['cancelled']

    def is_won(self):
        sale_won_states = self._sale_won_states()
        sale_lost_states = self._sale_lost_states()
        end_states = sale_won_states + sale_lost_states
        return (self.sales and all(s.state in end_states for s in self.sales)
                and any(s.state in sale_won_states for s in self.sales))

    def is_lost(self):
        sale_lost_states = self._sale_lost_states()
        return (self.sales
                and all(s.state in sale_lost_states for s in self.sales))

    @property
    def sale_amount(self):
        pool = Pool()
        Currency = pool.get('currency.currency')

        if not self.sales:
            return

        sale_lost_states = self._sale_lost_states()
        amount = 0
        for sale in self.sales:
            if sale.state not in sale_lost_states:
                amount += Currency.compute(sale.currency, sale.untaxed_amount,
                                           self.currency)
        return amount

    @classmethod
    def process(cls, opportunities):
        won = []
        lost = []
        converted = []
        for opportunity in opportunities:
            sale_amount = opportunity.sale_amount
            if opportunity.amount != sale_amount:
                opportunity.amount = sale_amount
            if opportunity.is_won():
                won.append(opportunity)
            elif opportunity.is_lost():
                lost.append(opportunity)
            elif (opportunity.state != 'converted' and opportunity.sales):
                converted.append(opportunity)
        cls.save(opportunities)
        if won:
            cls.won(won)
        if lost:
            cls.lost(lost)
        if converted:
            cls.convert(converted)
Exemple #4
0
class Work(sequence_ordered(), tree(separator='\\'), ModelSQL, ModelView):
    'Work Effort'
    __name__ = 'project.work'
    name = fields.Char('Name', required=True, select=True)
    type = fields.Selection([('project', 'Project'), ('task', 'Task')],
                            'Type',
                            required=True,
                            select=True)
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              select=True)
    party = fields.Many2One('party.party',
                            'Party',
                            states={
                                'invisible': Eval('type') != 'project',
                            },
                            depends=['type'])
    party_address = fields.Many2One('party.address',
                                    'Contact Address',
                                    domain=[('party', '=', Eval('party'))],
                                    states={
                                        'invisible': Eval('type') != 'project',
                                    },
                                    depends=['party', 'type'])
    timesheet_works = fields.One2Many('timesheet.work',
                                      'origin',
                                      'Timesheet Works',
                                      readonly=True,
                                      size=1)
    timesheet_available = fields.Function(
        fields.Boolean('Available on timesheets'),
        'get_timesheet_available',
        setter='set_timesheet_available')
    timesheet_start_date = fields.Function(fields.Date(
        'Timesheet Start',
        states={
            'invisible': ~Eval('timesheet_available'),
        },
        depends=['timesheet_available']),
                                           'get_timesheet_date',
                                           setter='set_timesheet_date')
    timesheet_end_date = fields.Function(fields.Date(
        'Timesheet End',
        states={
            'invisible': ~Eval('timesheet_available'),
        },
        depends=['timesheet_available']),
                                         'get_timesheet_date',
                                         setter='set_timesheet_date')
    timesheet_duration = fields.Function(
        fields.TimeDelta(
            'Duration',
            'company_work_time',
            help="Total time spent on this work and the sub-works"),
        'get_total')
    effort_duration = fields.TimeDelta('Effort',
                                       'company_work_time',
                                       help="Estimated Effort for this work")
    total_effort = fields.Function(
        fields.TimeDelta(
            'Total Effort',
            'company_work_time',
            help="Estimated total effort for this work and the sub-works"),
        'get_total')
    progress = fields.Float('Progress',
                            domain=[
                                'OR',
                                ('progress', '=', None),
                                [
                                    ('progress', '>=', 0),
                                    ('progress', '<=', 1),
                                ],
                            ],
                            help='Estimated progress for this work')
    total_progress = fields.Function(
        fields.Float(
            'Total Progress',
            digits=(16, 4),
            help='Estimated total progress for this work and the sub-works',
            states={
                'invisible': Eval('total_progress', None) == None,
            }), 'get_total')
    comment = fields.Text('Comment')
    parent = fields.Many2One('project.work',
                             'Parent',
                             left='left',
                             right='right',
                             ondelete='RESTRICT',
                             domain=[
                                 ('company', '=', Eval('company', -1)),
                             ],
                             depends=['company'])
    left = fields.Integer('Left', required=True, select=True)
    right = fields.Integer('Right', required=True, select=True)
    children = fields.One2Many('project.work',
                               'parent',
                               'Children',
                               domain=[
                                   ('company', '=', Eval('company', -1)),
                               ],
                               depends=['company'])
    state = fields.Selection([
        ('opened', 'Opened'),
        ('done', 'Done'),
    ],
                             'State',
                             required=True,
                             select=True)

    @staticmethod
    def default_type():
        return 'task'

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

    @staticmethod
    def default_state():
        return 'opened'

    @classmethod
    def default_left(cls):
        return 0

    @classmethod
    def default_right(cls):
        return 0

    @classmethod
    def __register__(cls, module_name):
        TimesheetWork = Pool().get('timesheet.work')
        cursor = Transaction().connection.cursor()
        table_project_work = cls.__table_handler__(module_name)
        project = cls.__table__()
        timesheet = TimesheetWork.__table__()

        work_exist = table_project_work.column_exist('work')
        add_parent = (not table_project_work.column_exist('parent')
                      and work_exist)
        add_company = (not table_project_work.column_exist('company')
                       and work_exist)
        add_name = (not table_project_work.column_exist('name') and work_exist)

        super(Work, cls).__register__(module_name)

        # Migration from 3.4: change effort into timedelta effort_duration
        if table_project_work.column_exist('effort'):
            cursor.execute(*project.select(
                project.id, project.effort, where=project.effort != Null))
            for id_, effort in cursor.fetchall():
                duration = datetime.timedelta(hours=effort)
                cursor.execute(
                    *project.update([project.effort_duration], [duration],
                                    where=project.id == id_))
            table_project_work.drop_column('effort')

        # Migration from 3.6: add parent, company, drop required on work,
        # fill name
        if add_parent:
            second_project = cls.__table__()
            query = project.join(
                timesheet, condition=project.work == timesheet.id).join(
                    second_project,
                    condition=timesheet.parent == second_project.work).select(
                        project.id, second_project.id)
            cursor.execute(*query)
            for id_, parent in cursor.fetchall():
                cursor.execute(*project.update([project.parent], [parent],
                                               where=project.id == id_))
            cls._rebuild_tree('parent', None, 0)
        if add_company:
            cursor.execute(*project.join(
                timesheet, condition=project.work == timesheet.id).select(
                    project.id, timesheet.company))
            for id_, company in cursor.fetchall():
                cursor.execute(*project.update([project.company], [company],
                                               where=project.id == id_))
        table_project_work.not_null_action('work', action='remove')
        if add_name:
            cursor.execute(*project.join(
                timesheet, condition=project.work == timesheet.id).select(
                    project.id, timesheet.name))
            for id_, name in cursor.fetchall():
                cursor.execute(*project.update([project.name], [name],
                                               where=project.id == id_))

        # Migration from 4.0: remove work
        if work_exist:
            table_project_work.drop_constraint('work_uniq')
            update = Transaction().connection.cursor()
            cursor.execute(*project.select(
                project.id, project.work, where=project.work != Null))
            for project_id, work_id in cursor:
                update.execute(*timesheet.update(
                    [timesheet.origin, timesheet.name],
                    ['%s,%s' % (cls.__name__, project_id), Null],
                    where=timesheet.id == work_id))
            table_project_work.drop_column('work')

    @classmethod
    def index_set_field(cls, name):
        index = super(Work, cls).index_set_field(name)
        if name in {'timesheet_start_date', 'timesheet_end_date'}:
            index = cls.index_set_field('timesheet_available') + 1
        return index

    @classmethod
    def validate(cls, works):
        super(Work, cls).validate(works)
        for work in works:
            work.check_state()

    def check_state(self):
        if (self.state == 'opened'
                and (self.parent and self.parent.state == 'done')):
            raise WorkValidationError(
                gettext('project.msg_work_invalid_parent_state',
                        child=self.rec_name,
                        parent=self.parent.rec_name))
        if self.state == 'done':
            for child in self.children:
                if child.state == 'opened':
                    raise WorkValidationError(
                        gettext('project.msg_work_invalid_children_state',
                                parent=self.rec_name,
                                child=child.rec_name))

    @property
    def effort_hours(self):
        if not self.effort_duration:
            return 0
        return self.effort_duration.total_seconds() / 60 / 60

    @property
    def total_effort_hours(self):
        if not self.total_effort:
            return 0
        return self.total_effort.total_seconds() / 60 / 60

    @property
    def timesheet_duration_hours(self):
        if not self.timesheet_duration:
            return 0
        return self.timesheet_duration.total_seconds() / 60 / 60

    @classmethod
    def default_timesheet_available(cls):
        return False

    def get_timesheet_available(self, name):
        return bool(self.timesheet_works)

    @classmethod
    def set_timesheet_available(cls, projects, name, value):
        pool = Pool()
        Timesheet = pool.get('timesheet.work')

        to_create = []
        to_delete = []
        for project in projects:
            if not project.timesheet_works and value:
                to_create.append({
                    'origin': str(project),
                    'company': project.company.id,
                })
            elif project.timesheet_works and not value:
                to_delete.extend(project.timesheet_works)

        if to_create:
            Timesheet.create(to_create)
        if to_delete:
            Timesheet.delete(to_delete)

    def get_timesheet_date(self, name):
        if self.timesheet_works:
            func = {
                'timesheet_start_date': min,
                'timesheet_end_date': max,
            }[name]
            return func(getattr(w, name) for w in self.timesheet_works)

    @classmethod
    def set_timesheet_date(cls, projects, name, value):
        pool = Pool()
        Timesheet = pool.get('timesheet.work')
        timesheets = [w for p in projects for w in p.timesheet_works]
        if timesheets:
            Timesheet.write(timesheets, {
                name: value,
            })

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

    @classmethod
    def get_total(cls, works, names):
        cursor = Transaction().connection.cursor()
        table = cls.__table__()

        works = cls.search([
            ('parent', 'child_of', [w.id for w in works]),
        ])
        work_ids = [w.id for w in works]
        parents = {}
        for sub_ids in grouped_slice(work_ids):
            where = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.select(table.id, table.parent, where=where))
            parents.update(cursor.fetchall())

        if 'total_progress' in names and 'total_effort' not in names:
            names = list(names)
            names.append('total_effort')

        result = {}
        for name in names:
            values = getattr(cls, '_get_%s' % name)(works)
            result[name] = cls.sum_tree(works, values, parents)

        if 'total_progress' in names:
            digits = cls.total_progress.digits[1]
            total_progress = result['total_progress']
            total_effort = result['total_effort']
            for work in works:
                if total_effort[work.id]:
                    total_progress[work.id] = round(
                        total_progress[work.id] /
                        (total_effort[work.id].total_seconds() / 60 / 60),
                        digits)
                else:
                    total_effort[work.id] = None
        return result

    @classmethod
    def _get_total_effort(cls, works):
        return {w.id: w.effort_duration or datetime.timedelta() for w in works}

    @classmethod
    def _get_timesheet_duration(cls, works):
        durations = {}
        for work in works:
            value = datetime.timedelta()
            for timesheet_work in work.timesheet_works:
                if timesheet_work.duration:
                    value += timesheet_work.duration
            durations[work.id] = value
        return durations

    @classmethod
    def _get_total_progress(cls, works):
        return {w.id: w.effort_hours * (w.progress or 0) for w in works}

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

    @classmethod
    def delete(cls, project_works):
        TimesheetWork = Pool().get('timesheet.work')

        # Get the timesheet works linked to the project works
        timesheet_works = [
            w for pw in project_works for w in pw.timesheet_works
        ]

        super(Work, cls).delete(project_works)

        if timesheet_works:
            with Transaction().set_context(_check_access=False):
                TimesheetWork.delete(timesheet_works)

    @classmethod
    def search_global(cls, text):
        for record, rec_name, icon in super(Work, cls).search_global(text):
            icon = icon or 'tryton-project'
            yield record, rec_name, icon
Exemple #5
0
class FiscalYear(Workflow, ModelSQL, ModelView):
    'Fiscal Year'
    __name__ = 'account.fiscalyear'
    name = fields.Char('Name', size=None, required=True)
    start_date = fields.Date('Starting Date',
                             required=True,
                             states=STATES,
                             domain=[('start_date', '<=',
                                      Eval('end_date', None))])
    end_date = fields.Date('Ending Date',
                           required=True,
                           states=STATES,
                           domain=[('end_date', '>=', Eval('start_date',
                                                           None))])
    periods = fields.One2Many('account.period',
                              'fiscalyear',
                              'Periods',
                              states=STATES,
                              domain=[
                                  ('company', '=', Eval('company')),
                              ],
                              order=[('start_date', 'ASC'), ('id', 'ASC')])
    state = fields.Selection([
        ('open', 'Open'),
        ('close', 'Close'),
        ('locked', 'Locked'),
    ],
                             'State',
                             readonly=True,
                             required=True,
                             sort=False)
    post_move_sequence = fields.Many2One(
        'ir.sequence',
        "Post Move Sequence",
        required=True,
        domain=[
            ('sequence_type', '=', Id('account',
                                      'sequence_type_account_move')),
            ('company', '=', Eval('company')),
        ])
    company = fields.Many2One('company.company',
                              "Company",
                              required=True,
                              select=True)
    icon = fields.Function(fields.Char("Icon"), 'get_icon')

    @classmethod
    def __setup__(cls):
        super(FiscalYear, cls).__setup__()
        cls._order.insert(0, ('start_date', 'DESC'))
        cls._transitions |= set((
            ('open', 'close'),
            ('close', 'locked'),
            ('close', 'open'),
        ))
        cls._buttons.update({
            'create_periods': {
                'invisible': ((Eval('state') != 'open')
                              | Eval('periods', [0])),
                'depends': ['state'],
            },
            'close': {
                'invisible': Eval('state') != 'open',
                'depends': ['state'],
            },
            'reopen': {
                'invisible': Eval('state') != 'close',
                'depends': ['state'],
            },
            'lock_': {
                'invisible': Eval('state') != 'close',
                'depends': ['state'],
            },
        })
        cls.__rpc__.update({
            'create_period': RPC(readonly=False, instantiate=0),
        })

    @staticmethod
    def default_state():
        return 'open'

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

    def get_icon(self, name):
        return {
            'open': 'tryton-account-open',
            'close': 'tryton-account-close',
            'locked': 'tryton-account-block',
        }.get(self.state)

    @classmethod
    def validate_fields(cls, fiscalyears, field_names):
        super().validate_fields(fiscalyears, field_names)
        cls.check_dates(fiscalyears, field_names)
        cls.check_post_move_sequence(fiscalyears, field_names)

    @classmethod
    def check_dates(cls, fiscalyears, field_names=None):
        if field_names and not (
                field_names & {'start_date', 'end_date', 'state', 'company'}):
            return
        transaction = Transaction()
        connection = transaction.connection
        cls.lock()
        cursor = connection.cursor()
        table = cls.__table__()
        for year in fiscalyears:
            cursor.execute(
                *table.select(table.id,
                              where=(((table.start_date <= year.start_date)
                                      & (table.end_date >= year.start_date))
                                     | ((table.start_date <= year.end_date)
                                        & (table.end_date >= year.end_date))
                                     | ((table.start_date >= year.start_date)
                                        & (table.end_date <= year.end_date)))
                              & (table.company == year.company.id)
                              & (table.id != year.id)))
            second_id = cursor.fetchone()
            if second_id:
                second = cls(second_id[0])
                raise FiscalYearDatesError(
                    gettext('account.msg_fiscalyear_overlap',
                            first=year.rec_name,
                            second=second.rec_name))
            if year.state == 'open':
                fiscalyears = cls.search([
                    ('start_date', '>=', year.end_date),
                    ('state', 'in', ['close', 'locked']),
                    ('company', '=', year.company.id),
                ],
                                         limit=1,
                                         order=[('start_date', 'ASC')])
                if fiscalyears:
                    fiscalyear, = fiscalyears
                    raise FiscalYearDatesError(
                        gettext('account.msg_open_fiscalyear_earlier',
                                open=year.rec_name,
                                close=fiscalyear.rec_name))

    @classmethod
    def check_post_move_sequence(cls, fiscalyears, field_names=None):
        if field_names and 'post_move_sequence' not in field_names:
            return
        for fiscalyear in fiscalyears:
            sequence = fiscalyear.post_move_sequence
            years = cls.search([
                ('post_move_sequence', '=', sequence.id),
                ('id', '!=', fiscalyear.id),
            ],
                               limit=1)
            if years:
                raise FiscalYearSequenceError(
                    gettext(
                        'account.msg_fiscalyear_different_post_move_sequence',
                        first=fiscalyear.rec_name,
                        second=years[0].rec_name))

    @classmethod
    def write(cls, *args):
        pool = Pool()
        Move = pool.get('account.move')
        actions = iter(args)
        for fiscalyears, values in zip(actions, actions):
            if values.get('post_move_sequence'):
                for fiscalyear in fiscalyears:
                    if (fiscalyear.post_move_sequence
                            and fiscalyear.post_move_sequence.id !=
                            values['post_move_sequence']):
                        if Move.search([
                            ('period.fiscalyear', '=', fiscalyear.id),
                            ('state', '=', 'posted'),
                        ]):
                            raise AccessError(
                                gettext(
                                    'account.'
                                    'msg_change_fiscalyear_post_move_sequence',
                                    fiscalyear=fiscalyear.rec_name))
        super(FiscalYear, cls).write(*args)

    @classmethod
    def delete(cls, fiscalyears):
        Period = Pool().get('account.period')
        Period.delete([p for f in fiscalyears for p in f.periods])
        super(FiscalYear, cls).delete(fiscalyears)

    @classmethod
    def create_period(cls, fiscalyears, interval=1, end_day=31):
        '''
        Create periods for the fiscal years with month interval
        '''
        Period = Pool().get('account.period')
        to_create = []
        for fiscalyear in fiscalyears:
            period_start_date = fiscalyear.start_date
            while period_start_date < fiscalyear.end_date:
                month_offset = 1 if period_start_date.day < end_day else 0
                period_end_date = (
                    period_start_date +
                    relativedelta(months=interval - month_offset) +
                    relativedelta(day=end_day))
                if period_end_date > fiscalyear.end_date:
                    period_end_date = fiscalyear.end_date
                name = period_start_date.strftime('%Y-%m')
                if name != period_end_date.strftime('%Y-%m'):
                    name += ' - ' + period_end_date.strftime('%Y-%m')
                to_create.append({
                    'name': name,
                    'start_date': period_start_date,
                    'end_date': period_end_date,
                    'fiscalyear': fiscalyear.id,
                    'type': 'standard',
                })
                period_start_date = period_end_date + relativedelta(days=1)
        if to_create:
            Period.create(to_create)

    @classmethod
    @ModelView.button_action('account.act_create_periods')
    def create_periods(cls, fiscalyears):
        pass

    @classmethod
    def find(cls, company_id, date=None, exception=True):
        '''
        Return the fiscal year for the company_id
            at the date or the current date.
        If exception is set the function will raise an exception
            if any fiscal year is found.
        '''
        pool = Pool()
        Lang = pool.get('ir.lang')
        Date = pool.get('ir.date')

        if not date:
            with Transaction().set_context(company=company_id):
                date = Date.today()
        fiscalyears = cls.search([
            ('start_date', '<=', date),
            ('end_date', '>=', date),
            ('company', '=', company_id),
        ],
                                 order=[('start_date', 'DESC')],
                                 limit=1)
        if not fiscalyears:
            if exception:
                lang = Lang.get()
                raise FiscalYearNotFoundError(
                    gettext('account.msg_no_fiscalyear_date',
                            date=lang.strftime(date)))
            else:
                return None
        return fiscalyears[0].id

    def get_deferral(self, account):
        'Computes deferrals for accounts'
        pool = Pool()
        Currency = pool.get('currency.currency')
        Deferral = pool.get('account.account.deferral')

        if not account.type:
            return
        if not account.deferral:
            if not Currency.is_zero(self.company.currency, account.balance):
                raise FiscalYearCloseError(
                    gettext(
                        'account'
                        '.msg_close_fiscalyear_account_balance_not_zero',
                        account=account.rec_name))
        else:
            deferral = Deferral()
            deferral.account = account
            deferral.fiscalyear = self
            deferral.debit = account.debit
            deferral.credit = account.credit
            deferral.line_count = account.line_count
            deferral.amount_second_currency = account.amount_second_currency
            return deferral

    @classmethod
    @ModelView.button
    @Workflow.transition('close')
    def close(cls, fiscalyears):
        '''
        Close a fiscal year
        '''
        pool = Pool()
        Period = pool.get('account.period')
        Account = pool.get('account.account')
        Deferral = pool.get('account.account.deferral')

        # Prevent create new fiscal year or period
        cls.lock()
        Period.lock()

        deferrals = []
        for fiscalyear in fiscalyears:
            if cls.search([
                ('end_date', '<=', fiscalyear.start_date),
                ('state', '=', 'open'),
                ('company', '=', fiscalyear.company.id),
            ]):
                raise FiscalYearCloseError(
                    gettext('account.msg_close_fiscalyear_earlier',
                            fiscalyear=fiscalyear.rec_name))

            periods = Period.search([
                ('fiscalyear', '=', fiscalyear.id),
            ])
            Period.close(periods)

            with Transaction().set_context(fiscalyear=fiscalyear.id,
                                           date=None,
                                           cumulate=True,
                                           journal=None):
                accounts = Account.search([
                    ('company', '=', fiscalyear.company.id),
                ])
                for account in accounts:
                    deferral = fiscalyear.get_deferral(account)
                    if deferral:
                        deferrals.append(deferral)
        Deferral.save(deferrals)

    @classmethod
    @ModelView.button
    @Workflow.transition('open')
    def reopen(cls, fiscalyears):
        '''
        Re-open a fiscal year
        '''
        Deferral = Pool().get('account.account.deferral')

        for fiscalyear in fiscalyears:
            if cls.search([
                ('start_date', '>=', fiscalyear.end_date),
                ('state', '!=', 'open'),
                ('company', '=', fiscalyear.company.id),
            ]):
                raise FiscalYearReOpenError(
                    gettext('account.msg_reopen_fiscalyear_later',
                            fiscalyear=fiscalyear.rec_name))

            deferrals = Deferral.search([
                ('fiscalyear', '=', fiscalyear.id),
            ])
            Deferral.delete(deferrals)

    @classmethod
    @ModelView.button
    @Workflow.transition('locked')
    def lock_(cls, fiscalyears):
        pool = Pool()
        Period = pool.get('account.period')
        periods = Period.search([
            ('fiscalyear', 'in', [f.id for f in fiscalyears]),
        ])
        Period.lock_(periods)
Exemple #6
0
class DeathCertificate(ModelSQL, ModelView):

    __name__ = 'gnuhealth.death_certificate'

    serializer = fields.Text('Doc String', readonly=True)

    document_digest = fields.Char('Digest',
                                  readonly=True,
                                  help="Original Document Digest")

    digest_status = fields.Function(fields.Boolean('Altered',
        states={
        'invisible': Not(Equal(Eval('state'),'done')),
        },
        help="This field will be set whenever parts of" \
        " the main original document has been changed." \
        " Please note that the verification is done only on selected" \
        " fields." ),
        'check_digest')

    serializer_current = fields.Function(
        fields.Text('Current Doc',
                    states={
                        'invisible': Not(Bool(Eval('digest_status'))),
                    }), 'check_digest')

    digest_current = fields.Function(
        fields.Char('Current Hash',
                    states={
                        'invisible': Not(Bool(Eval('digest_status'))),
                    }), 'check_digest')

    digital_signature = fields.Text('Digital Signature', readonly=True)

    @classmethod
    def __setup__(cls):
        super(DeathCertificate, cls).__setup__()
        cls._buttons.update({
            'generate_death_certificate': {
                'invisible': Not(Equal(Eval('state'), 'signed')),
            },
        })
        ''' Allow calling the set_signature method via RPC '''
        cls.__rpc__.update({
            'set_signature': RPC(readonly=False),
        })

    @classmethod
    @ModelView.button
    def generate_death_certificate(cls, certificates):
        certificate = certificates[0]
        HealthProf = Pool().get('gnuhealth.healthprofessional')

        # Change the state of the certificate to "Done"

        serial_doc = cls.get_serial(certificate)

        signing_hp = HealthProf.get_health_professional()
        if not signing_hp:
            cls.raise_user_error(
                "No health professional associated to this user !")

        cls.write(
            certificates, {
                'serializer': serial_doc,
                'document_digest': HealthCrypto().gen_hash(serial_doc),
                'state': 'done',
            })

    @classmethod
    def get_serial(cls, certificate):

        underlying_conds = []

        for condition in certificate.underlying_conditions:
            cond = []
            cond = [
                str(condition.condition.rec_name), condition.interval,
                condition.unit_of_time
            ]

            underlying_conds.append(cond)

        data_to_serialize = {
            'certificate': str(certificate.code) or '',
            'Date': str(certificate.dod) or '',
            'HP': certificate.signed_by \
                and str(certificate.signed_by.rec_name) or '',
            'Person': str(certificate.name.rec_name),
            'Person_dob':str(certificate.name.dob) or '',
            'Person_ID': str(certificate.name.ref) or '',
            'Cod': str(certificate.cod.rec_name),
            'Underlying_conditions': underlying_conds or '',
            'Autopsy': certificate.autopsy,
            'Type_of_death': str(certificate.type_of_death),
            'Place_of_death': str(certificate.place_of_death),
            'Country': str(certificate.country.rec_name) or '',
            'Country_subdivision': certificate.country_subdivision \
                and str(certificate.country_subdivision.rec_name) or '',
            'Observations': str(certificate.observations),
             }

        serialized_doc = str(HealthCrypto().serialize(data_to_serialize))

        return serialized_doc

    @classmethod
    def set_signature(cls, data, signature):
        """
        Set the clearsigned signature
        """
        doc_id = data['id']

        cls.write([cls(doc_id)], {
            'digital_signature': signature,
        })

    def check_digest(self, name):
        result = ''
        serial_doc = self.get_serial(self)
        if (name == 'digest_status' and self.document_digest):
            if (HealthCrypto().gen_hash(serial_doc) == self.document_digest):
                result = False
            else:
                ''' Return true if the document has been altered'''
                result = True
        if (name == 'digest_current'):
            result = HealthCrypto().gen_hash(serial_doc)

        if (name == 'serializer_current'):
            result = serial_doc

        return result

    # Hide the group holding all the digital signature until signed

    @classmethod
    def view_attributes(cls):
        return [('//group[@id="group_current_string"]', 'states', {
            'invisible': ~Eval('digest_status'),
        })]
Exemple #7
0
class PatientPrescriptionOrder(ModelSQL, ModelView):
    """ Add the serialized and hash fields to the
    prescription order document"""

    __name__ = 'gnuhealth.prescription.order'

    serializer = fields.Text('Doc String', readonly=True)

    document_digest = fields.Char('Digest',
                                  readonly=True,
                                  help="Original Document Digest")

    digest_status = fields.Function(fields.Boolean('Altered',
        states={
        'invisible': Not(Equal(Eval('state'),'done')),
        },
        help="This field will be set whenever parts of" \
        " the main original document has been changed." \
        " Please note that the verification is done only on selected" \
        " fields." ),
        'check_digest')

    serializer_current = fields.Function(
        fields.Text('Current Doc',
                    states={
                        'invisible': Not(Bool(Eval('digest_status'))),
                    }), 'check_digest')

    digest_current = fields.Function(
        fields.Char('Current Hash',
                    states={
                        'invisible': Not(Bool(Eval('digest_status'))),
                    }), 'check_digest')

    digital_signature = fields.Text('Digital Signature', readonly=True)

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    def __setup__(cls):
        super(PatientPrescriptionOrder, cls).__setup__()
        cls._buttons.update({
            'generate_prescription': {
                'invisible': Equal(Eval('state'), 'validated'),
            },
            'create_prescription': {
                'invisible':
                Or(Equal(Eval('state'), 'done'),
                   Equal(Eval('state'), 'validated'))
            },
        })
        ''' Allow calling the set_signature method via RPC '''
        cls.__rpc__.update({
            'set_signature': RPC(readonly=False),
        })

    @classmethod
    @ModelView.button
    def generate_prescription(cls, prescriptions):
        prescription = prescriptions[0]

        # Change the state of the prescription to "Validated"

        serial_doc = cls.get_serial(prescription)

        cls.write(
            prescriptions, {
                'serializer': serial_doc,
                'document_digest': HealthCrypto().gen_hash(serial_doc),
                'state': 'validated',
            })

    @classmethod
    def get_serial(cls, prescription):

        presc_line = []

        for line in prescription.prescription_line:
            line_elements = [
                line.medicament and line.medicament.name.name or '', line.dose
                or '', line.route and line.route.name or '',
                line.form and line.form.name or '',
                line.indication and line.indication.name or '',
                line.short_comment or ''
            ]

            presc_line.append(line_elements)

        data_to_serialize = {
            'Prescription': str(prescription.prescription_id) or '',
            'Date': str(prescription.prescription_date) or '',
            'HP': str(prescription.healthprof.rec_name),
            'Patient': str(prescription.patient.rec_name),
            'Patient_ID': str(prescription.patient.name.ref) or '',
            'Prescription_line': str(presc_line),
            'Notes': str(prescription.notes),
        }

        serialized_doc = str(HealthCrypto().serialize(data_to_serialize))

        return serialized_doc

    @classmethod
    def set_signature(cls, data, signature):
        """
        Set the clearsigned signature
        """

        doc_id = data['id']

        cls.write([cls(doc_id)], {
            'digital_signature': signature,
        })

    def check_digest(self, name):
        result = ''
        serial_doc = self.get_serial(self)
        if (name == 'digest_status' and self.document_digest):
            if (HealthCrypto().gen_hash(serial_doc) == self.document_digest):
                result = False
            else:
                ''' Return true if the document has been altered'''
                result = True
        if (name == 'digest_current'):
            result = HealthCrypto().gen_hash(serial_doc)

        if (name == 'serializer_current'):
            result = serial_doc

        return result

    # Hide the group holding validation information when state is
    # not validated

    @classmethod
    def view_attributes(cls):
        return [('//group[@id="prescription_digest"]', 'states', {
            'invisible': Not(Eval('state') == 'validated'),
        })]
Exemple #8
0
class MoveLineTemplate(ModelSQL, ModelView):
    'Account Move Line Template'
    __name__ = 'account.move.line.template'
    move = fields.Many2One('account.move.template', 'Move', required=True)
    operation = fields.Selection([
        ('debit', 'Debit'),
        ('credit', 'Credit'),
    ],
                                 'Operation',
                                 required=True)
    amount = fields.Char(
        'Amount',
        required=True,
        help="A python expression that will be evaluated with the keywords.")
    account = fields.Many2One('account.account',
                              'Account',
                              required=True,
                              domain=[
                                  ('type', '!=', None),
                                  ('closed', '!=', True),
                                  ('company', '=', Eval('_parent_move',
                                                        {}).get('company',
                                                                -1)),
                              ])
    party = fields.Char('Party',
                        states={
                            'required': Eval('party_required', False),
                            'invisible': ~Eval('party_required', False),
                        },
                        depends=['party_required'],
                        help="The name of the 'Party' keyword.")
    party_required = fields.Function(fields.Boolean('Party Required'),
                                     'on_change_with_party_required')
    description = fields.Char(
        'Description',
        help="Keywords values substitutions are identified "
        "by braces ('{' and '}').")
    taxes = fields.One2Many('account.tax.line.template', 'line', 'Taxes')

    @fields.depends('account')
    def on_change_with_party_required(self, name=None):
        if self.account:
            return self.account.party_required
        return False

    def get_line(self, values):
        'Return the move line for the keyword values'
        pool = Pool()
        Line = pool.get('account.move.line')
        Keyword = pool.get('account.move.template.keyword')

        line = Line()
        amount = simple_eval(decistmt(self.amount),
                             functions={'Decimal': Decimal},
                             names=values)
        amount = self.move.company.currency.round(amount)
        if self.operation == 'debit':
            line.debit = amount
        else:
            line.credit = amount
        line.account = self.account
        if self.party:
            line.party = values.get(self.party)
        if self.description:
            line.description = self.description.format(
                **dict(Keyword.format_values(self.move, values)))
        line.tax_lines = [t.get_line(values) for t in self.taxes]

        return line
Exemple #9
0
class User(metaclass=PoolMeta):
    __name__ = 'res.user'
    main_company = fields.Many2One(
        'company.company',
        'Main Company',
        help="Grant access to the company and its children.")
    company = fields.Many2One('company.company',
                              'Current Company',
                              domain=[('parent', 'child_of',
                                       [Eval('main_company')], 'parent')],
                              depends=['main_company'],
                              help="Select the company to work for.")
    companies = fields.Function(
        fields.One2Many('company.company', None, 'Companies'), 'get_companies')
    employees = fields.Many2Many(
        'res.user-company.employee',
        'user',
        'employee',
        'Employees',
        help="Add employees to grant the user access to them.")
    employee = fields.Many2One(
        'company.employee',
        'Current Employee',
        domain=[
            ('company', '=', Eval('company', -1)),
            ('id', 'in', Eval('employees', [])),
        ],
        depends=['company', 'employees'],
        help="Select the employee to make the user behave as such.")

    @classmethod
    def __setup__(cls):
        super(User, cls).__setup__()
        cls._context_fields.insert(0, 'company')
        cls._context_fields.insert(0, 'employee')

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

    @classmethod
    def default_company(cls):
        return cls.default_main_company()

    @classmethod
    def get_companies(cls, users, name):
        Company = Pool().get('company.company')
        companies = {}
        company_childs = {}
        for user in users:
            companies[user.id] = []
            company = None
            if user.company:
                company = user.company
            elif user.main_company:
                company = user.main_company
            if company:
                if company in company_childs:
                    company_ids = company_childs[company]
                else:
                    company_ids = list(
                        map(
                            int,
                            Company.search([
                                ('parent', 'child_of', [company.id]),
                            ])))
                    company_childs[company] = company_ids
                if company_ids:
                    companies[user.id].extend(company_ids)
        return companies

    def get_status_bar(self, name):
        status = super(User, self).get_status_bar(name)
        if self.company:
            status += ' - %s [%s]' % (self.company.rec_name,
                                      self.company.currency.name)
        return status

    @fields.depends('main_company')
    def on_change_main_company(self):
        self.company = self.main_company
        self.employee = None

    @fields.depends('company', 'employees')
    def on_change_company(self):
        Employee = Pool().get('company.employee')
        self.employee = None
        if self.company and self.employees:
            employees = Employee.search([
                ('id', 'in', [e.id for e in self.employees]),
                ('company', '=', self.company.id),
            ])
            if employees:
                self.employee = employees[0]

    @classmethod
    def _get_preferences(cls, user, context_only=False):
        res = super(User, cls)._get_preferences(user,
                                                context_only=context_only)
        if not context_only:
            res['main_company'] = None
            if user.main_company:
                res['main_company'] = user.main_company.id
                res['main_company.rec_name'] = user.main_company.rec_name
            res['employees'] = [e.id for e in user.employees]
        if user.employee:
            res['employee'] = user.employee.id
            res['employee.rec_name'] = user.employee.rec_name
        if user.company:
            res['company'] = user.company.id
            res['company.rec_name'] = user.company.rec_name
        return res

    @classmethod
    def get_preferences_fields_view(cls):
        pool = Pool()
        Company = pool.get('company.company')

        res = super(User, cls).get_preferences_fields_view()
        res = copy.deepcopy(res)

        def convert2selection(definition, name):
            del definition[name]['relation']
            definition[name]['type'] = 'selection'
            selection = []
            definition[name]['selection'] = selection
            return selection

        if 'company' in res['fields']:
            selection = convert2selection(res['fields'], 'company')
            selection.append((None, ''))
            user = cls(Transaction().user)
            if user.main_company:
                companies = Company.search([
                    ('parent', 'child_of', [user.main_company.id], 'parent'),
                ])
                for company in companies:
                    selection.append((company.id, company.rec_name))
        return res

    @classmethod
    def read(cls, ids, fields_names=None):
        Company = Pool().get('company.company')
        user_id = Transaction().user
        if user_id == 0 and 'user' in Transaction().context:
            user_id = Transaction().context['user']
        result = super(User, cls).read(ids, fields_names=fields_names)
        if (fields_names and
            (('company' in fields_names and 'company' in Transaction().context)
             or ('employee' in fields_names
                 and 'employee' in Transaction().context))):
            values = None
            if int(user_id) in ids:
                for vals in result:
                    if vals['id'] == int(user_id):
                        values = vals
                        break
            if values:
                if ('company' in fields_names
                        and 'company' in Transaction().context):
                    main_company_id = values.get('main_company')
                    if not main_company_id:
                        main_company_id = cls.read(
                            [user_id], ['main_company'])[0]['main_company']
                    companies = Company.search([
                        ('parent', 'child_of', [main_company_id]),
                    ])
                    company_id = Transaction().context['company']
                    if ((company_id
                         and company_id in list(map(int, companies)))
                            or not company_id or Transaction().user == 0):
                        values['company'] = company_id
                if ('employee' in fields_names
                        and 'employee' in Transaction().context):
                    employees = values.get('employees')
                    if not employees:
                        employees = cls.read([user_id],
                                             ['employees'])[0]['employees']
                    employee_id = Transaction().context['employee']
                    if ((employee_id and employee_id in employees)
                            or not employee_id or Transaction().user == 0):
                        values['employee'] = employee_id
        return result

    @classmethod
    def write(cls, *args):
        pool = Pool()
        Rule = pool.get('ir.rule')
        super(User, cls).write(*args)
        # Restart the cache on the domain_get method
        Rule._domain_get_cache.clear()
class DocumentRequestCash(ModelSQL, ModelView):
    _name = 'ekd.document.head.request'

    budget_ref = fields.Function(fields.Many2One("ekd.account.budget",
                                                 'Budget'),
                                 'get_budget_ref',
                                 setter='set_budget_ref')
    budget = fields.Many2One('ekd.account.budget',
                             'Budget',
                             states=_RECEIVED_STATES,
                             depends=_RECEIVED_DEPENDS)
    lines = fields.One2Many('ekd.document.line.request',
                            'requestcash',
                            'Lines Request',
                            states=_RECEIVED_STATES,
                            depends=_RECEIVED_DEPENDS,
                            context={
                                'document_ref': Eval('document_ref'),
                                'budget_ref': Eval('budget_ref'),
                                'budget': Eval('budget'),
                                'currency_digits': Eval('currency_digits')
                            })
    document_ref = fields.Reference(
        'Base',
        selection='documents_get',
        select=1,
        states={'readonly': Not(In(Eval('state_doc'), ['empty', 'draft']))},
        depends=['state_doc'],
        on_change=['document_ref'])

    def __init__(self):
        super(DocumentRequestCash, self).__init__()

    def get_budget_ref(self, ids, name):
        if not ids:
            return
        res = {}.fromkeys(ids, False)
        for line in self.browse(ids):
            if line.budget:
                res[line.id] = line.budget.id
            elif line.document_ref:
                model, model_id = line.document_ref.split(',')
                if model_id == '0':
                    continue
                if model:
                    if model == 'ekd.account.budget':
                        res[line.id] = int(model_id)
                    elif model == 'ekd.project':
                        model_obj = self.pool.get(model)
                        model_brw = model_obj.browse(int(model_id))
                        if model_brw and model_brw.budget:
                            res[line.id] = model_brw.budget.id
                    else:
                        raise Exception('Error', 'Unknown-model: %s' % (model))
        return res

    def set_budget_ref(self, id, name, value):
        if not value:
            return
        self.write(id, {
            'budget': value,
        })

    def on_change_document_ref(self, value):
        res = {}
        if value.get('document_ref'):
            model, model_id = value.get('document_ref').split(',')
            if model_id == '0':
                return res
            if model == 'ekd.project':
                model_obj = self.pool.get(model)
                model_ids = model_obj.browse(int(model_id))
                if model_ids.manager:
                    res['manager'] = model_ids.manager.id
                if model_ids.employee:
                    res['employee'] = model_ids.employee.id
                if model_ids.budget:
                    res['budget_ref'] = model_ids.budget.id
                    res['budget'] = model_ids.budget.id
            elif model == 'ekd.account.budget':
                res['budget_ref'] = int(model_id)
                res['budget'] = int(model_id)
        return res
Exemple #11
0
class Location(DeactivableMixin, tree(), ModelSQL, ModelView):
    "Stock Location"
    __name__ = 'stock.location'
    _default_warehouse_cache = Cache('stock.location.default_warehouse',
                                     context=False)

    name = fields.Char("Name",
                       size=None,
                       required=True,
                       states=STATES,
                       depends=DEPENDS,
                       translate=True)
    code = fields.Char("Code",
                       size=None,
                       states=STATES,
                       depends=DEPENDS,
                       select=True,
                       help="The internal identifier used for the location.")
    address = fields.Many2One("party.address",
                              "Address",
                              states={
                                  'invisible': Eval('type') != 'warehouse',
                                  'readonly': ~Eval('active'),
                              },
                              depends=['type', 'active'])
    type = fields.Selection([
        ('supplier', 'Supplier'),
        ('customer', 'Customer'),
        ('lost_found', 'Lost and Found'),
        ('warehouse', 'Warehouse'),
        ('storage', 'Storage'),
        ('production', 'Production'),
        ('drop', 'Drop'),
        ('view', 'View'),
    ],
                            'Location type',
                            states=STATES,
                            depends=DEPENDS)
    type_string = type.translated('type')
    parent = fields.Many2One("stock.location",
                             "Parent",
                             select=True,
                             left="left",
                             right="right",
                             states={
                                 'invisible': Eval('type') == 'warehouse',
                             },
                             depends=['type'],
                             help="Used to add structure above the location.")
    left = fields.Integer('Left', required=True, select=True)
    right = fields.Integer('Right', required=True, select=True)
    childs = fields.One2Many("stock.location",
                             "parent",
                             "Children",
                             help="Used to add structure below the location.")
    flat_childs = fields.Boolean(
        "Flat Children",
        help="Check to enforce a single level of children with no "
        "grandchildren.")
    warehouse = fields.Function(fields.Many2One('stock.location', 'Warehouse'),
                                'get_warehouse')
    input_location = fields.Many2One("stock.location",
                                     "Input",
                                     states={
                                         'invisible':
                                         Eval('type') != 'warehouse',
                                         'readonly': ~Eval('active'),
                                         'required':
                                         Eval('type') == 'warehouse',
                                     },
                                     domain=[
                                         ('type', '=', 'storage'),
                                         [
                                             'OR',
                                             ('parent', 'child_of',
                                              [Eval('id')]),
                                             ('parent', '=', None),
                                         ],
                                     ],
                                     depends=['type', 'active', 'id'],
                                     help="Where incoming stock is received.")
    output_location = fields.Many2One(
        "stock.location",
        "Output",
        states={
            'invisible': Eval('type') != 'warehouse',
            'readonly': ~Eval('active'),
            'required': Eval('type') == 'warehouse',
        },
        domain=[('type', '=', 'storage'),
                [
                    'OR', ('parent', 'child_of', [Eval('id')]),
                    ('parent', '=', None)
                ]],
        depends=['type', 'active', 'id'],
        help="Where outgoing stock is sent from.")
    storage_location = fields.Many2One(
        "stock.location",
        "Storage",
        states={
            'invisible': Eval('type') != 'warehouse',
            'readonly': ~Eval('active'),
            'required': Eval('type') == 'warehouse',
        },
        domain=[('type', 'in', ['storage', 'view']),
                [
                    'OR', ('parent', 'child_of', [Eval('id')]),
                    ('parent', '=', None)
                ]],
        depends=['type', 'active', 'id'],
        help="The top level location where stock is stored.")
    picking_location = fields.Many2One(
        'stock.location',
        'Picking',
        states={
            'invisible': Eval('type') != 'warehouse',
            'readonly': ~Eval('active'),
        },
        domain=[
            ('type', '=', 'storage'),
            ('parent', 'child_of', [Eval('storage_location', -1)]),
        ],
        depends=['type', 'active', 'storage_location'],
        help="Where stock is picked from.\n"
        "Leave empty to use the storage location.")
    quantity = fields.Function(fields.Float(
        'Quantity', help="The amount of stock in the location."),
                               'get_quantity',
                               searcher='search_quantity')
    forecast_quantity = fields.Function(fields.Float(
        'Forecast Quantity',
        help="The amount of stock expected to be in the location."),
                                        'get_quantity',
                                        searcher='search_quantity')
    cost_value = fields.Function(
        fields.Numeric('Cost Value',
                       help="The value of the stock in the location."),
        'get_cost_value')

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

        parent_domain = [[
            'OR',
            ('parent.flat_childs', '=', False),
            ('parent', '=', None),
        ]]
        childs_domain = [
            If(Eval('flat_childs', False), ('childs', '=', None), ()),
        ]
        childs_mapping = cls._childs_domain()
        for type_, allowed_parents in cls._parent_domain().items():
            parent_domain.append(
                If(Eval('type') == type_, ('type', 'in', allowed_parents), ()))
            childs_domain.append(
                If(
                    Eval('type') == type_,
                    ('type', 'in', childs_mapping[type_]), ()))
        cls.parent.domain = parent_domain
        cls.childs.domain = childs_domain
        cls.childs.depends.extend(['flat_childs', 'type'])

    @classmethod
    def _parent_domain(cls):
        '''Returns a dict with location types as keys and a list of allowed
        parent location types as values'''
        return {
            'customer': ['customer'],
            'supplier': ['supplier'],
            'production': ['production'],
            'lost_found': ['lost_found'],
            'view': ['warehouse', 'view', 'storage'],
            'storage': ['warehouse', 'view', 'storage'],
            'warehouse': [''],
        }

    @classmethod
    def _childs_domain(cls):
        childs_domain = {}
        for type_, allowed_parents in cls._parent_domain().items():
            for parent in allowed_parents:
                childs_domain.setdefault(parent, [])
                childs_domain[parent].append(type_)
        return childs_domain

    @classmethod
    def __register__(cls, module_name):
        super(Location, cls).__register__(module_name)

        table = cls.__table_handler__(module_name)
        table.index_action(['left', 'right'], 'add')

    @classmethod
    def validate(cls, locations):
        super(Location, cls).validate(locations)
        inactives = []
        for location in locations:
            location.check_type_for_moves()
            if not location.active:
                inactives.append(location)
        cls.check_inactive(inactives)

    def check_type_for_moves(self):
        """ Check locations with moves have types compatible with moves. """
        invalid_move_types = ['warehouse', 'view']
        Move = Pool().get('stock.move')
        if self.type in invalid_move_types:
            # Use root to compute for all companies
            with Transaction().set_user(0):
                moves = Move.search([
                    [
                        'OR',
                        ('to_location', '=', self.id),
                        ('from_location', '=', self.id),
                    ],
                    ('state', 'not in', ['staging', 'draft']),
                ])
            if moves:
                raise LocationValidationError(
                    gettext('stock.msg_location_invalid_type_for_moves',
                            location=self.rec_name,
                            type=self.type_string))

    @classmethod
    def check_inactive(cls, locations):
        "Check inactive location are empty"
        assert all(not l.active for l in locations)
        empty = cls.get_empty_locations(locations)
        non_empty = set(locations) - set(empty)
        if non_empty:
            raise LocationValidationError(
                gettext('stock.msg_location_inactive_not_empty',
                        location=next(iter(non_empty)).rec_name))

    @classmethod
    def get_empty_locations(cls, locations=None):
        pool = Pool()
        Move = pool.get('stock.move')
        if locations is None:
            locations = cls.search([])
        if not locations:
            return []
        location_ids = list(map(int, locations))
        # Use root to compute for all companies
        # and ensures inactive locations are in the query
        with Transaction().set_user(0), \
                Transaction().set_context(active_test=False):
            query = Move.compute_quantities_query(location_ids,
                                                  with_childs=True)
            quantities = Move.compute_quantities(query,
                                                 location_ids,
                                                 with_childs=True)
            empty = set(location_ids)
            for (location_id, product), quantity in quantities.items():
                if quantity:
                    empty.discard(location_id)
            for sub_ids in grouped_slice(list(empty)):
                sub_ids = list(sub_ids)
                moves = Move.search([
                    ('state', 'not in', ['done', 'cancel']),
                    [
                        'OR',
                        ('from_location', 'in', sub_ids),
                        ('to_location', 'in', sub_ids),
                    ],
                ])
                for move in moves:
                    for location in [move.from_location, move.to_location]:
                        empty.discard(location.id)
        return cls.browse(empty)

    @staticmethod
    def default_left():
        return 0

    @staticmethod
    def default_right():
        return 0

    @classmethod
    def default_flat_childs(cls):
        return False

    @staticmethod
    def default_type():
        return 'storage'

    @classmethod
    def check_xml_record(cls, records, values):
        return True

    def get_warehouse(self, name):
        # Order by descending left to get the first one in the tree
        with Transaction().set_context(active_test=False):
            locations = self.search([
                ('parent', 'parent_of', [self.id]),
                ('type', '=', 'warehouse'),
            ],
                                    order=[('left', 'DESC')])
        if locations:
            return locations[0].id

    @classmethod
    def get_default_warehouse(cls):
        warehouse = Transaction().context.get('warehouse')
        if warehouse:
            return warehouse

        warehouse = cls._default_warehouse_cache.get(None, -1)
        if warehouse == -1:
            warehouses = cls.search([
                ('type', '=', 'warehouse'),
            ], limit=2)
            if len(warehouses) == 1:
                warehouse = warehouses[0].id
            else:
                warehouse = None
            cls._default_warehouse_cache.set(None, warehouse)
        return warehouse

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

    @classmethod
    def get_quantity(cls, locations, name):
        pool = Pool()
        Product = pool.get('product.product')
        Date_ = pool.get('ir.date')
        trans_context = Transaction().context

        def valid_context(name):
            return (trans_context.get(name) is not None
                    and isinstance(trans_context[name], int))

        if not any(map(valid_context, ['product', 'product_template'])):
            return {l.id: None for l in locations}

        context = {}
        if (name == 'quantity' and (trans_context.get(
                'stock_date_end', datetime.date.max) > Date_.today())):
            context['stock_date_end'] = Date_.today()

        if name == 'forecast_quantity':
            context['forecast'] = True
            if not trans_context.get('stock_date_end'):
                context['stock_date_end'] = datetime.date.max

        if trans_context.get('product') is not None:
            grouping = ('product', )
            grouping_filter = ([trans_context['product']], )
            key = trans_context['product']
        else:
            grouping = ('product.template', )
            grouping_filter = ([trans_context['product_template']], )
            key = trans_context['product_template']
        pbl = {}
        for sub_locations in grouped_slice(locations):
            location_ids = [l.id for l in sub_locations]
            with Transaction().set_context(context):
                pbl.update(
                    Product.products_by_location(
                        location_ids,
                        grouping=grouping,
                        grouping_filter=grouping_filter,
                        with_childs=trans_context.get('with_childs', True)))

        return dict((loc.id, pbl.get((loc.id, key), 0)) for loc in locations)

    @classmethod
    def search_quantity(cls, name, domain):
        _, operator_, operand = domain
        operator_ = {
            '=': operator.eq,
            '>=': operator.ge,
            '>': operator.gt,
            '<=': operator.le,
            '<': operator.lt,
            '!=': operator.ne,
            'in': lambda v, l: v in l,
            'not in': lambda v, l: v not in l,
        }.get(operator_, lambda v, l: False)

        ids = []
        for location in cls.search([]):
            if operator_(getattr(location, name), operand):
                ids.append(location.id)
        return [('id', 'in', ids)]

    @classmethod
    def get_cost_value(cls, locations, name):
        pool = Pool()
        Product = pool.get('product.product')
        Template = pool.get('product.template')
        trans_context = Transaction().context
        cost_values = {l.id: None for l in locations}

        def valid_context(name):
            return (trans_context.get(name) is not None
                    and isinstance(trans_context[name], int))

        if not any(map(valid_context, ['product', 'product_template'])):
            return cost_values

        def get_record():
            if trans_context.get('product') is not None:
                return Product(trans_context['product'])
            else:
                return Template(trans_context['product_template'])

        context = {}
        if 'stock_date_end' in trans_context:
            # Use the last cost_price of the day
            context['_datetime'] = datetime.datetime.combine(
                trans_context['stock_date_end'], datetime.time.max)
            # The date could be before the product creation
            record = get_record()
            if record.create_date > context['_datetime']:
                return cost_values
        with Transaction().set_context(context):
            cost_price = get_record().cost_price
        # The template may have more than one product
        if cost_price is not None:
            for location in locations:
                cost_values[location.id] = (Decimal(str(location.quantity)) *
                                            cost_price)
        return cost_values

    @classmethod
    def _set_warehouse_parent(cls, locations):
        '''
        Set the parent of child location of warehouse if not set
        '''
        to_update = set()
        to_save = []
        for location in locations:
            if location.type == 'warehouse':
                if not location.input_location.parent:
                    to_update.add(location.input_location)
                if not location.output_location.parent:
                    to_update.add(location.output_location)
                if not location.storage_location.parent:
                    to_update.add(location.storage_location)
                if to_update:
                    for child_location in to_update:
                        child_location.parent = location
                        to_save.append(child_location)
                    to_update.clear()
        cls.save(to_save)

    @classmethod
    def create(cls, vlist):
        locations = super(Location, cls).create(vlist)
        cls._set_warehouse_parent(locations)
        cls._default_warehouse_cache.clear()
        return locations

    @classmethod
    def write(cls, *args):
        super(Location, cls).write(*args)
        locations = sum(args[::2], [])
        cls._set_warehouse_parent(locations)
        cls._default_warehouse_cache.clear()

        ids = [l.id for l in locations]
        warehouses = cls.search([('type', '=', 'warehouse'),
                                 [
                                     'OR',
                                     ('storage_location', 'in', ids),
                                     ('input_location', 'in', ids),
                                     ('output_location', 'in', ids),
                                 ]])

        fields = ('storage_location', 'input_location', 'output_location')
        wh2childs = {}
        for warehouse in warehouses:
            in_out_sto = (getattr(warehouse, f).id for f in fields)
            for location in locations:
                if location.id not in in_out_sto:
                    continue
                childs = wh2childs.setdefault(
                    warehouse.id,
                    cls.search([
                        ('parent', 'child_of', warehouse.id),
                    ]))
                if location not in childs:
                    raise LocationValidationError(
                        gettext('stock.msg_location_child_of_warehouse',
                                location=location.rec_name,
                                warehouse=warehouse.rec_name))

    @classmethod
    def delete(cls, *args):
        super().delete(*args)
        cls._default_warehouse_cache.clear()

    @classmethod
    def copy(cls, locations, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()

        res = []
        for location in locations:
            if location.type == 'warehouse':

                wh_default = default.copy()
                wh_default['type'] = 'view'
                wh_default['input_location'] = None
                wh_default['output_location'] = None
                wh_default['storage_location'] = None
                wh_default['childs'] = None

                new_location, = super(Location, cls).copy([location],
                                                          default=wh_default)

                with Transaction().set_context(
                        cp_warehouse_locations={
                            'input_location': location.input_location.id,
                            'output_location': location.output_location.id,
                            'storage_location': location.storage_location.id,
                        },
                        cp_warehouse_id=new_location.id):
                    cls.copy(location.childs,
                             default={'parent': new_location.id})
                cls.write([new_location], {
                    'type': 'warehouse',
                })
            else:
                new_location, = super(Location, cls).copy([location],
                                                          default=default)
                warehouse_locations = Transaction().context.get(
                    'cp_warehouse_locations') or {}
                if location.id in warehouse_locations.values():
                    cp_warehouse = cls(
                        Transaction().context['cp_warehouse_id'])
                    for field, loc_id in warehouse_locations.items():
                        if loc_id == location.id:
                            cls.write([cp_warehouse], {
                                field: new_location.id,
                            })

            res.append(new_location)
        return res
class DocumentRequestCashLine(ModelSQL, ModelView):
    _name = 'ekd.document.line.request'

    budget_ref = fields.Function(
        fields.Many2One("ekd.account.budget", 'Budget'), 'get_budget_ref')
    # Ошибка в домене ??????????????
    budget_line = fields.Many2One(
        'ekd.account.budget.line',
        'Budget Line',
        states={'readonly': Not(Bool(Eval('budget_ref')))},
        domain=[
            #                                "('budget','=', context.get('budget_ref',False))",
            ('budget', '=', Eval('budget_ref')),
            ('direct_line', '=', 'expense'),
            ('type_line', '=', 'line')
        ],
        on_change=['budget_line'],
        depends=['budget_ref'])
    amount_budget = fields.Function(
        fields.Numeric('Amount in Budget', digits=(16, 2)), 'get_budget')

    #    name = fields.Char('Description')
    #    analytic = fields.Many2One('ekd.account.analytic', 'Analytic Account')

    def default_budget_ref(self):
        #raise Exception(str(Transaction().context))
        if Transaction().context.get('budget_ref'):
            return Transaction().context.get('budget_ref')
        elif Transaction().context.get('budget'):
            return Transaction().context.get('budget')
        return False

    def get_budget(self, ids, names):
        res = {}
        for line in self.browse(ids):
            for name in names:
                res.setdefault(name, {}.fromkeys(ids, Decimal('0.0')))
                if name == 'amount_budget' and line.budget_line:
                    res[name][line.id] = line.budget_line.amount
        return res

    def get_budget_ref(self, ids, name):
        context = Transaction().context
        if context.get('budget_ref'):
            return {}.fromkeys(ids, context.get('budget_ref'))
        res = {}.fromkeys(ids, False)
        for line in self.browse(ids):
            if line.requestcash.budget_ref:
                res[line.id] = line.requestcash.budget_ref.id
        #raise Exception(str(res))
        return res

    def on_change_budget_line(self, vals):
        if vals.get('budget_line'):
            budget_line_obj = self.pool.get('ekd.account.budget.line')
            budget_line = budget_line_obj.browse(vals.get('budget_line'))
            return {
                'name': budget_line.name,
                'analytic': budget_line.analytic.id,
                'amount_budget': budget_line.amount,
                'amount': budget_line.amount
            }
        else:
            return {
                'analytic': False,
                'amount_budget': Decimal('0.0'),
                'amount': Decimal('0.0')
            }

    def on_change_analytic(self, vals):
        if vals.get('budget_line'):
            return {}
        elif vals.get('analytic'):
            analytic_obj = self.pool.get('ekd.account.analytic')
            analytic_id = analytic_obj.browse(vals.get('analytic'))
            if vals.get('name'):
                return {
                    'name': "%s - (%s)" % (vals.get('name'), analytic_id.name)
                }
            else:
                return {'name': analytic_id.name}
        else:
            return {}
Exemple #13
0
class DictSchemaMixin(object):
    __slots__ = ()
    _rec_name = 'string'
    name = fields.Char(lazy_gettext('ir.msg_dict_schema_name'), required=True)
    string = fields.Char(lazy_gettext('ir.msg_dict_schema_string'),
                         translate=True,
                         required=True)
    help = fields.Text(lazy_gettext('ir.msg_dict_schema_help'), translate=True)
    type_ = fields.Selection([
        ('boolean', lazy_gettext('ir.msg_dict_schema_boolean')),
        ('integer', lazy_gettext('ir.msg_dict_schema_integer')),
        ('char', lazy_gettext('ir.msg_dict_schema_char')),
        ('float', lazy_gettext('ir.msg_dict_schema_float')),
        ('numeric', lazy_gettext('ir.msg_dict_schema_numeric')),
        ('date', lazy_gettext('ir.msg_dict_schema_date')),
        ('datetime', lazy_gettext('ir.msg_dict_schema_datetime')),
        ('selection', lazy_gettext('ir.msg_dict_schema_selection')),
        ('multiselection', lazy_gettext('ir.msg_dict_schema_multiselection')),
    ],
                             lazy_gettext('ir.msg_dict_schema_type'),
                             required=True)
    digits = fields.Integer(lazy_gettext('ir.msg_dict_schema_digits'),
                            states={
                                'invisible':
                                ~Eval('type_').in_(['float', 'numeric']),
                            },
                            depends=['type_'])
    domain = fields.Char(lazy_gettext('ir.msg_dict_schema_domain'))
    selection = fields.Text(
        lazy_gettext('ir.msg_dict_schema_selection'),
        states={
            'invisible': ~Eval('type_').in_(['selection', 'multiselection']),
        },
        translate=True,
        depends=['type_'],
        help=lazy_gettext('ir.msg_dict_schema_selection_help'))
    selection_sorted = fields.Boolean(
        lazy_gettext('ir.msg_dict_schema_selection_sorted'),
        states={
            'invisible': ~Eval('type_').in_(['selection', 'multiselection']),
        },
        depends=['type_'],
        help=lazy_gettext('ir.msg_dict_schema_selection_sorted_help'))
    selection_json = fields.Function(
        fields.Char(lazy_gettext('ir.msg_dict_schema_selection_json'),
                    states={
                        'invisible':
                        ~Eval('type_').in_(['selection', 'multiselection']),
                    },
                    depends=['type_']), 'get_selection_json')
    _relation_fields_cache = Cache('_dict_schema_mixin.get_relation_fields')

    @classmethod
    def __setup__(cls):
        super(DictSchemaMixin, cls).__setup__()
        cls.__rpc__.update({
            'get_keys': RPC(instantiate=0),
        })

    @staticmethod
    def default_digits():
        return 2

    @staticmethod
    def default_selection_sorted():
        return True

    @fields.depends('name', 'string')
    def on_change_string(self):
        if not self.name and self.string:
            self.name = slugify(self.string.lower(), hyphenate='_')

    @classmethod
    def validate(cls, schemas):
        super(DictSchemaMixin, cls).validate(schemas)
        cls.check_domain(schemas)
        cls.check_selection(schemas)

    @classmethod
    def check_domain(cls, schemas):
        for schema in schemas:
            if not schema.domain:
                continue
            try:
                value = PYSONDecoder().decode(schema.domain)
            except Exception:
                raise DomainError(
                    gettext('ir.msg_dict_schema_invalid_domain',
                            schema=schema.rec_name))
            if not isinstance(value, list):
                raise DomainError(
                    gettext('ir.msg_dict_schema_invalid_domain',
                            schema=schema.rec_name))

    @classmethod
    def check_selection(cls, schemas):
        for schema in schemas:
            if schema.type_ not in {'selection', 'multiselection'}:
                continue
            try:
                dict(json.loads(schema.get_selection_json()))
            except Exception:
                raise SelectionError(
                    gettext('ir.msg_dict_schema_invalid_selection',
                            schema=schema.rec_name))

    def get_selection_json(self, name=None):
        db_selection = self.selection or ''
        selection = [[w.strip() for w in v.split(':', 1)]
                     for v in db_selection.splitlines() if v]
        return json.dumps(selection, separators=(',', ':'))

    @classmethod
    def get_keys(cls, records):
        pool = Pool()
        Config = pool.get('ir.configuration')
        keys = []
        for record in records:
            new_key = {
                'id': record.id,
                'name': record.name,
                'string': record.string,
                'help': record.help,
                'type': record.type_,
                'domain': record.domain,
                'sequence': getattr(record, 'sequence', record.name),
            }
            if record.type_ in {'selection', 'multiselection'}:
                with Transaction().set_context(language=Config.get_language()):
                    english_key = cls(record.id)
                    selection = OrderedDict(
                        json.loads(english_key.selection_json))
                selection.update(dict(json.loads(record.selection_json)))
                new_key['selection'] = list(selection.items())
                new_key['sort'] = record.selection_sorted
            elif record.type_ in ('float', 'numeric'):
                new_key['digits'] = (16, record.digits)
            keys.append(new_key)
        return keys

    @classmethod
    def get_relation_fields(cls):
        if not config.get('dict', cls.__name__, default=True):
            return {}
        fields = cls._relation_fields_cache.get(cls.__name__)
        if fields is not None:
            return fields
        keys = cls.get_keys(cls.search([]))
        fields = {k['name']: k for k in keys}
        cls._relation_fields_cache.set(cls.__name__, fields)
        return fields

    @classmethod
    def create(cls, vlist):
        records = super().create(vlist)
        cls._relation_fields_cache.clear()
        return records

    @classmethod
    def write(cls, *args):
        super().write(*args)
        cls._relation_fields_cache.clear()

    @classmethod
    def delete(cls, records):
        super().delete(records)
        cls._relation_fields_cache.clear()
class InvoiceEdiLine(ModelSQL, ModelView):
    'Invoice Edi Line'
    __name__ = 'invoice.edi.line'

    edi_invoice = fields.Many2One('invoice.edi', 'Invoice', ondelete='CASCADE')
    code = fields.Char('Code')
    code_type = fields.Selection([
        (None, ''),
        ('EAN', 'EAN'),
        ('EAN8', 'EAN8'),
        ('EAN13', 'EAN13'),
        ('EAN14', 'EAN14'),
        ('DUN14', 'DUN14'),
    ], 'Code Type')
    sequence = fields.Integer('Sequence')
    supplier_code = fields.Char('Supplier Code')
    purchaser_code = fields.Char('Purchaser Code')
    lot_number = fields.Char('Lot Number')
    serial_number = fields.Char('Serial Number')
    customer_code = fields.Char('Customer Code')
    producer_code = fields.Char('Producer Code')
    national_code = fields.Char('National Code')
    hibc_code = fields.Char('Healh Industry Bar Code')
    description = fields.Char('Description')
    characteristic = fields.Selection([(None, ''), ('M', 'Goods'), ('C', 'C')],
                                      'Characteristic')
    qualifier = fields.Selection([(None, ''), ('F', 'Free Description')],
                                 'Qualifier  ')
    quantities = fields.One2Many('invoice.edi.line.quantity', 'line',
                                 'Quantities')
    delivery_date = fields.Char('Delivery Date')
    base_amount = fields.Numeric('Base Amount', digits=(16, 2))
    total_amount = fields.Numeric('Total Amount', digits=(16, 2))
    unit_price = fields.Numeric('Unit Price', digits=(16, 4))
    gross_price = fields.Numeric('Gross Price', digits=(16, 4))
    references = fields.One2Many('invoice.edi.reference', 'edi_invoice_line',
                                 'References')
    taxes = fields.One2Many('invoice.edi.tax', 'line', 'Taxes')
    discounts = fields.One2Many('invoice.edi.discount', 'invoice_edi_line',
                                'Discounts')
    product = fields.Many2One('product.product', 'Product')
    quantity = fields.Function(fields.Numeric('Quantity', digits=(16, 4)),
                               'invoiced_quantity')
    invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line')
    note = fields.Text('Note')

    @classmethod
    def __setup__(cls):
        super(InvoiceEdiLine, cls).__setup__()

    def search_related(self, edi_invoice):
        pool = Pool()
        Barcode = pool.get('product.code')
        REF = Pool().get('invoice.edi.reference')
        # ('barcode', '=', self.code_type) Remove this from domain after some
        # received
        domain = [('number', '=', self.code)]
        barcode = Barcode.search(domain, limit=1)
        if not barcode:
            return
        product = barcode[0].product
        self.product = product

        purchases = [
            x.origin for x in edi_invoice.references
            if x.type_ == 'ON' and x.origin
        ]
        self.references = []
        for purchase in purchases:
            for move in purchase.moves:
                if move.state != 'done':
                    continue
                if move.product == product:  # TODO: check for quantity?
                    ref = REF()
                    ref.type_ = 'move'
                    ref.origin = 'stock.move,%s' % move.id
                    self.references += (ref, )

    def get_line(self):
        if self.edi_invoice.type_ == '381':  # CREDIT:
            return self.get_line_credit()
        if self.references is None or len(self.references) != 1:
            raise UserError(
                gettext('account_invoice_edi.confirm_invoice_with_reference',
                        line=self.description))

        move, = self.references
        invoice_lines = [x for x in move.origin.invoice_lines if not x.invoice]
        if not invoice_lines or len(invoice_lines) != 1:
            raise UserError(
                gettext('account_invoice_edi.confirm_invoice_with_invoice',
                        line=self.description))

        invoice_line, = invoice_lines

        # JUst invoice wat system expect to see differences.
        # invoice_line.gross_unit_price = self.gross_price or self.unit_price
        # invoice_line.unit_price = self.unit_price
        # if self.unit_price and self.gross_price:
        #     invoice_line.discount = Decimal(1 -
        #         self.unit_price/self.gross_price).quantize(Decimal('.01'))
        # else:
        #     invoice_line.unit_price = Decimal(
        #         self.base_amount / self.quantity).quantize(Decimal('0.0001'))

        self.invoice_line = invoice_line
        return invoice_line

    def get_line_credit(self):
        move = self.references and self.references[0]
        invoice_lines = [
            x for x in move and move.origin.invoice_lines or []
            if not x.invoice
        ]
        invoice_line = invoice_lines and invoice_lines[0]

        Line = Pool().get('account.invoice.line')
        line = Line()
        line.product = self.product
        line.invoice_type = 'in'
        line.quantity = self.quantity
        if self.base_amount < 0:
            line.quantity = -self.quantity
        line.party = self.edi_invoice.party
        line.type = 'line'
        line.on_change_product()
        line.on_change_account()
        line.gross_unit_price = self.gross_price or self.unit_price
        line.unit_price = self.unit_price
        if self.unit_price and self.gross_price:
            line.discount = Decimal(1 - self.unit_price /
                                    self.gross_price).quantize(Decimal('.01'))
        else:
            line.unit_price = Decimal(
                self.base_amount / self.quantity).quantize(Decimal('0.0001'))
        if invoice_line:
            line.origin = invoice_line
        self.invoice_line = line
        return line

    def invoiced_quantity(self, name):
        for q in self.quantities:
            if q.type_ == '47':
                return q.quantity
        return Decimal('0')

    def read_LIN(self, message):
        def _get_code_type(code):
            for code_type in ('EAN8', 'EAN13', 'EAN'):
                check_code_ean = 'check_code_' + code_type.lower()
                if getattr(barcodenumber, check_code_ean)(code):
                    return code_type
            if len(code) == 14:
                return 'EAN14'
            # TODO DUN14

        self.code = message.pop(0) if message else ''
        code_type = message.pop(0) if message else ''
        if code_type == 'EN':
            self.code_type = _get_code_type(self.code)
        # Some times the provider send the EAN13 without left zeros
        # and the EAN is an EAN13 but the check fail becasue it have
        # less digits.
        if self.code_type == 'EN' and len(self.code) < 13:
            code = self.code.zfill(13)
            if getattr(barcodenumber, 'check_code_ean13')(code):
                self.code = code
                self.code_type = 'EAN13'
        if message:
            self.sequence = int(message.pop(0))

    def read_PIALIN(self, message):
        self.supplier_code = message.pop(0) if message else ''
        if message:
            self.purchaser_code = message.pop(0)
        if message:
            self.lot_number = message.pop(0)
        if message:
            self.serial_number = message.pop(0)
        if message:
            self.customer_code = message.pop(0)
        if message:
            self.producer_code = message.pop(0)
        if message:
            self.national_code = message.pop(0)
        if message:
            self.hibc_code = message.pop(0)

    def read_IMDLIN(self, message):
        self.description = message.pop(0) if message else ''
        self.characteristic = message.pop(0) if message else ''
        self.qualifier = message.pop(0) if message else ''

    def read_QTYLIN(self, message):
        QTY = Pool().get('invoice.edi.line.quantity')
        qty = QTY()
        qty.type_ = message.pop(0) if message else ''
        qty.quantity = to_decimal(message.pop(0), 4) if message else Decimal(0)
        if qty.type_ == '47':
            self.quantity = qty.quantity
        if message:
            qty.uom_char = message.pop(0)
        if not getattr(self, 'quantities', False):
            self.quantities = []
        self.quantities += (qty, )

    def read_DTMLINE(self, message):
        self.delivery_date = to_date(message.pop(0)) if message else None

    def read_MOALIN(self, message):
        self.base_amount = to_decimal(
            message.pop(0)) if message else Decimal(0)
        if message:
            self.total_amount = to_decimal(message.pop(0))

    def read_PRILIN(self, message):
        type_ = message.pop(0)
        if type_ == 'AAA':
            self.unit_price = to_decimal(message.pop(0),
                                         4) if message else Decimal(0)
        elif type_ == 'AAB':
            self.gross_price = to_decimal(message.pop(0),
                                          4) if message else Decimal(0)

    def read_RFFLIN(self, message):
        REF = Pool().get('invoice.edi.reference')
        ref = REF()
        ref.type_ = message.pop(0) if message else ''
        ref.value = message.pop(0) if message else ''
        ref.search_reference()
        if message:
            ref.line_number = message.pop(0) if message else ''
        if not getattr(self, 'references', False):
            self.references = []
        self.references += (ref, )

    def read_TAXLIN(self, message):
        Tax = Pool().get('invoice.edi.tax')
        tax = Tax()
        tax.type_ = message.pop(0) if message else ''
        tax.percent = to_decimal(message.pop(0)) if message else Decimal(0)
        if message:
            tax.tax_amount = to_decimal(message.pop(0))
        if not getattr(self, 'taxes', False):
            self.taxes = []
        self.taxes += (tax, )

    def read_TXTLIN(self, message):
        self.note = message.pop(0) if message else ''

    def read_ALCLIN(self, message):
        Discount = Pool().get('invoice.edi.discount')
        discount = Discount()
        discount.type_ = message.pop(0) if message else ''
        discount.sequence = int(message.pop(0) or 0) if message else 0
        discount.discount = message.pop(0) if message else ''
        discount.percent = to_decimal(
            message.pop(0)) if message else Decimal(0)
        discount.amount = to_decimal(message.pop(0)) if message else Decimal(0)
        if not getattr(self, 'discounts', False):
            self.discounts = []
        self.discounts += (discount, )
Exemple #15
0
class PatientDisabilityAssessment(ModelSQL, ModelView):
    'Patient Disability Information'
    __name__ = 'gnuhealth.patient.disability_assessment'

    patient = fields.Many2One('gnuhealth.patient', 'Patient', required=True)

    assessment_date = fields.Date('Date')

    assessment = fields.Char('Code')

    crutches = fields.Boolean('Crutches')
    wheelchair = fields.Boolean('Wheelchair')

    uxo = fields.Function(fields.Boolean('UXO'), 'get_uxo_status')
    amputee = fields.Function(fields.Boolean('Amputee'), 'get_amputee_status')
    amputee_since = fields.Function(fields.Date('Since'), 'get_amputee_date')

    notes = fields.Text('Notes', help="Extra Information")

    hand_function = fields.Selection([
        (None, ''),
        ('0', 'No impairment'),
        ('1', 'Mild impairment'),
        ('2', 'Moderate impairment'),
        ('3', 'Severe impairment'),
        ('4', 'Complete impairment'),
    ],
                                     'Hand',
                                     sort=False)

    visual_function = fields.Selection([
        (None, ''),
        ('0', 'No impairment'),
        ('1', 'Mild impairment'),
        ('2', 'Moderate impairment'),
        ('3', 'Severe impairment'),
        ('4', 'Complete impairment'),
    ],
                                       'Visual',
                                       sort=False)

    speech_function = fields.Selection([
        (None, ''),
        ('0', 'No impairment'),
        ('1', 'Mild impairment'),
        ('2', 'Moderate impairment'),
        ('3', 'Severe impairment'),
        ('4', 'Complete impairment'),
    ],
                                       'Speech',
                                       sort=False)

    hearing_function = fields.Selection([
        (None, ''),
        ('0', 'No impairment'),
        ('1', 'Mild impairment'),
        ('2', 'Moderate impairment'),
        ('3', 'Severe impairment'),
        ('4', 'Complete impairment'),
    ],
                                        'Hearing',
                                        sort=False)

    cognitive_function = fields.Selection([
        (None, ''),
        ('0', 'No impairment'),
        ('1', 'Mild impairment'),
        ('2', 'Moderate impairment'),
        ('3', 'Severe impairment'),
        ('4', 'Complete impairment'),
    ],
                                          'Cognitive',
                                          sort=False)

    locomotor_function = fields.Selection([
        (None, ''),
        ('0', 'No impairment'),
        ('1', 'Mild impairment'),
        ('2', 'Moderate impairment'),
        ('3', 'Severe impairment'),
        ('4', 'Complete impairment'),
    ],
                                          'Mobility',
                                          sort=False)

    activity_participation = fields.Selection([
        (None, ''),
        ('0', 'No impairment'),
        ('1', 'Mild impairment'),
        ('2', 'Moderate impairment'),
        ('3', 'Severe impairment'),
        ('4', 'Complete impairment'),
    ],
                                              'A & P',
                                              sort=False)

    body_functions = fields.One2Many('gnuhealth.body_function.assessment',
                                     'assessment',
                                     'Body Functions Impairments')

    body_structures = fields.One2Many('gnuhealth.body_structure.assessment',
                                      'assessment',
                                      'Body Structures Impairments')

    activity_and_participation = fields.One2Many(
        'gnuhealth.activity.assessment', 'assessment',
        'Activities and Participation Impairments')

    environmental_factor = fields.One2Many('gnuhealth.environment.assessment',
                                           'assessment',
                                           'Environmental Factors Barriers')

    healthprof = fields.Many2One('gnuhealth.healthprofessional',
                                 'Health Prof',
                                 help="Authorized health professional")

    def get_uxo_status(self, name):
        return self.patient.uxo

    def get_amputee_status(self, name):
        return self.patient.amputee

    def get_amputee_date(self, name):
        return self.patient.amputee_since

    @staticmethod
    def default_assessment_date():
        return date.today()

    @staticmethod
    def default_healthprof():
        pool = Pool()
        HealthProf = pool.get('gnuhealth.healthprofessional')
        hp = HealthProf.get_health_professional()
        return hp
Exemple #16
0
class AccountLiquidation(ModelSQL, ModelView):
    'Account Liquidation'
    __name__ = 'account.liquidation'
    _rec_name = 'number'

    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        states=_STATES,
        select=True,
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        depends=_DEPENDS)
    type = fields.Selection(_TYPE,
                            'Type',
                            select=True,
                            required=True,
                            states={
                                'readonly': ((Eval('state') != 'draft')),
                            },
                            depends=['state'])
    number = fields.Char('Number', size=None, select=True)

    reference = fields.Char('Reference',
                            size=None,
                            states=_STATES,
                            depends=_DEPENDS)
    description = fields.Char('Description',
                              size=None,
                              states=_STATES,
                              depends=_DEPENDS)

    state = fields.Selection([
        ('draft', 'Draft'),
        ('validated', 'Confirm'),
        ('posted', 'Posted'),
    ],
                             'State',
                             readonly=True)

    liquidation_date = fields.Date('Liquidation Date',
                                   states={
                                       'readonly':
                                       Eval('state').in_(['posted', 'cancel']),
                                       'required':
                                       Eval('state').in_(['posted']),
                                   },
                                   depends=['state'])
    accounting_date = fields.Date('Accounting Date',
                                  states=_STATES,
                                  depends=_DEPENDS)
    party = fields.Many2One('party.party',
                            'Party',
                            required=True,
                            states=_STATES,
                            depends=_DEPENDS)
    party_lang = fields.Function(fields.Char('Party Language'),
                                 'on_change_with_party_lang')
    liquidation_address = fields.Many2One('party.address',
                                          'Liquidation Address',
                                          required=True,
                                          states=_STATES,
                                          depends=['state', 'party'],
                                          domain=[('party', '=', Eval('party'))
                                                  ])
    currency = fields.Many2One('currency.currency',
                               'Currency',
                               required=True,
                               states={
                                   'readonly': ((Eval('state') != 'draft')),
                               },
                               depends=['state'])
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    currency_date = fields.Function(fields.Date('Currency Date'),
                                    'on_change_with_currency_date')
    journal = fields.Many2One('account.journal',
                              'Journal',
                              required=True,
                              states=_STATES,
                              depends=_DEPENDS)
    move = fields.Many2One('account.move', 'Move', readonly=True)
    account = fields.Many2One('account.account',
                              'Account',
                              required=True,
                              states=_STATES,
                              depends=_DEPENDS)
    taxes = fields.One2Many('account.liquidation.tax',
                            'liquidation',
                            'Tax Lines',
                            states=_STATES,
                            depends=_DEPENDS)
    comment = fields.Text('Comment', states=_STATES, depends=_DEPENDS)

    untaxed_amount = fields.Function(fields.Numeric(
        'Untaxed',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
                                     'get_amount',
                                     searcher='search_untaxed_amount')
    tax_amount = fields.Function(fields.Numeric(
        'Tax',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
                                 'get_amount',
                                 searcher='search_tax_amount')
    total_amount = fields.Function(fields.Numeric(
        'Total liquidation',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
                                   'get_amount',
                                   searcher='search_total_amount')

    @classmethod
    def __setup__(cls):
        super(AccountLiquidation, cls).__setup__()

        cls._error_messages.update({
            'delete_liquidation':
            'You can not delete a liquidation that is posted!',
            'no_liquidation_sequence':
            ('There is no liquidation sequence for '
             'liquidation "%(liquidation)s" on the period/fiscal year '
             '"%(period)s".'),
        })

        cls._buttons.update({
            'validate_liquidation': {
                'invisible': Eval('state') != 'draft',
            },
            'post': {
                'invisible': (Eval('state') == 'posted'),
                'readonly': ~Eval('taxes', [0]),
                #'readonly': Not(Bool(Eval('taxes'))
            },
        })
        cls._order.insert(0, ('liquidation_date', 'DESC'))

    @staticmethod
    def default_state():
        return 'draft'

    @fields.depends('currency')
    def on_change_with_currency_digits(self, name=None):
        if self.currency:
            return self.currency.digits
        return 2

    @fields.depends('party')
    def on_change_with_party_lang(self, name=None):
        Config = Pool().get('ir.configuration')
        if self.party:
            if self.party.lang:
                return self.party.lang.code
        return Config.get_language()

    def get_tax_context(self):
        context = {}
        if self.party and self.party.lang:
            context['language'] = self.party.lang.code
        return context

    @classmethod
    def get_amount(cls, invoices, names):
        pool = Pool()
        InvoiceTax = pool.get('account.liquidation.tax')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')
        cursor = Transaction().cursor

        untaxed_amount = dict((i.id, _ZERO) for i in invoices)
        tax_amount = dict((i.id, _ZERO) for i in invoices)
        total_amount = dict((i.id, _ZERO) for i in invoices)

        type_name = cls.tax_amount._field.sql_type().base
        tax = InvoiceTax.__table__()
        to_round = False
        for sub_ids in grouped_slice(invoices):
            red_sql = reduce_ids(tax.liquidation, sub_ids)
            cursor.execute(
                *tax.select(tax.liquidation,
                            Coalesce(Sum(tax.amount), 0).as_(type_name),
                            where=red_sql,
                            group_by=tax.liquidation))
            for invoice_id, sum_ in cursor.fetchall():
                # SQLite uses float for SUM
                if not isinstance(sum_, Decimal):
                    sum_ = Decimal(str(sum_))
                    to_round = True
                tax_amount[invoice_id] = sum_
        # Float amount must be rounded to get the right precision
        if to_round:
            for invoice in invoices:
                tax_amount[invoice.id] = invoice.currency.round(
                    tax_amount[invoice.id])

        invoices_move = set()
        invoices_no_move = set()
        for invoice in invoices:
            if invoice.move:
                invoices_move.add(invoice.id)
            else:
                invoices_no_move.add(invoice.id)
        invoices_move = cls.browse(invoices_move)
        invoices_no_move = cls.browse(invoices_no_move)

        type_name = cls.total_amount._field.sql_type().base
        invoice = cls.__table__()
        move = Move.__table__()
        line = MoveLine.__table__()
        to_round = False
        for sub_ids in grouped_slice(invoices_move):
            red_sql = reduce_ids(invoice.id, sub_ids)
            cursor.execute(
                *invoice.join(move, condition=invoice.move == move.id).join(
                    line, condition=move.id == line.move).select(
                        invoice.id,
                        Coalesce(
                            Sum(
                                Case((line.second_currency == invoice.currency,
                                      Abs(line.amount_second_currency) *
                                      Sign(line.debit - line.credit)),
                                     else_=line.debit -
                                     line.credit)), 0).cast(type_name),
                        where=(invoice.account == line.account) & red_sql,
                        group_by=invoice.id))
            for invoice_id, sum_ in cursor.fetchall():
                # SQLite uses float for SUM
                if not isinstance(sum_, Decimal):
                    sum_ = Decimal(str(sum_))
                    to_round = True
                total_amount[invoice_id] = sum_

        for invoice in invoices_move:
            # Float amount must be rounded to get the right precision
            if to_round:
                total_amount[invoice.id] = invoice.currency.round(
                    total_amount[invoice.id])
            untaxed_amount[invoice.id] = (total_amount[invoice.id] -
                                          tax_amount[invoice.id])

        for invoice in invoices_no_move:
            total_amount[invoice.id] = (untaxed_amount[invoice.id] +
                                        tax_amount[invoice.id])

        result = {
            'untaxed_amount': untaxed_amount,
            'tax_amount': tax_amount,
            'total_amount': total_amount,
        }

        for key in result.keys():
            if key not in names:
                del result[key]
        return result

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

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

    @staticmethod
    def default_journal():
        pool = Pool()
        Journal = pool.get('account.journal')
        journal = Journal.search([('type', '=', 'expense')])

        for j in journal:
            return j.id

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

    def set_number(self):
        pool = Pool()
        Period = pool.get('account.period')
        Sequence = pool.get('ir.sequence.strict')
        Date = pool.get('ir.date')

        if self.number:
            return

        test_state = True

        accounting_date = self.accounting_date or self.liquidation_date
        period_id = Period.find(self.company.id,
                                date=accounting_date,
                                test_state=test_state)
        period = Period(period_id)
        sequence = period.get_invoice_sequence(self.type)
        if not sequence:
            self.raise_user_error('no_liquidation_sequence', {
                'liquidation': self.rec_name,
                'period': period.rec_name,
            })
        with Transaction().set_context(
                date=self.liquidation_date or Date.today()):
            number = Sequence.get_id(sequence.id)
            vals = {'number': number}
            if (not self.liquidation_date
                    and self.type in ('out_liquidation')):
                vals['liquidation_date'] = Transaction().context['date']
        self.write([self], vals)

    @classmethod
    def delete(cls, liquidations):
        if not liquidations:
            return True
        for liquidation in liquidations:
            if liquidation.state == 'posted':
                cls.raise_user_error('delete_liquidation')
        return super(AccountLiquidation, cls).delete(liquidations)

    def prepare_liquidation_lines(self):
        pool = Pool()
        Period = pool.get('account.period')
        Move = pool.get('account.move')
        Liquidation = pool.get('account.liquidation')
        amount = Decimal(0.0)
        move_lines = []
        line_move_ids = []
        move, = Move.create([{
            'period':
            Period.find(self.company.id, date=self.liquidation_date),
            'journal':
            self.journal.id,
            'date':
            self.liquidation_date,
            'origin':
            str(self),
        }])

        self.write([self], {
            'move': move.id,
        })

        for tax in self.taxes:
            amount += tax.amount

        if self.type == 'out_liquidation':
            debit = Decimal('0.00')
            credit = amount
        else:
            debit = self.total_amount
            credit = Decimal('0.00')
        move_lines.append({
            'description':
            self.number,
            'debit':
            debit,
            'credit':
            credit,
            'account':
            self.account.id,
            'move':
            move.id,
            'journal':
            self.journal.id,
            'period':
            Period.find(self.company.id, date=self.liquidation_date),
        })
        if self.taxes:
            for tax in self.taxes:
                if self.type == 'out_liquidation':
                    debit = tax.amount
                    credit = Decimal('0.00')

                move_lines.append({
                    'description':
                    tax.description,
                    'debit':
                    debit,
                    'credit':
                    credit,
                    'account':
                    tax.account.id,
                    'move':
                    move.id,
                    'journal':
                    self.journal.id,
                    'party':
                    self.party,
                    'period':
                    Period.find(self.company.id, date=self.liquidation_date),
                })
        return move_lines

    def posted(self, move_lines):
        pool = Pool()
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')
        created_lines = MoveLine.create(move_lines)
        Move.post([self.move])
        return True

    @classmethod
    @ModelView.button
    @Workflow.transition('validated')
    def validate_invoice(cls, liquidations):
        for liquidation in liquidations:
            invoice.create_move()

    @classmethod
    @ModelView.button
    def post(cls, liquidations):
        for liquidation in liquidations:
            liquidation.set_number()
            move_lines = liquidation.prepare_liquidation_lines()
            liquidation.posted(move_lines)
        cls.write(liquidations, {'state': 'posted'})
Exemple #17
0
class DiseaseNotification(ModelView, ModelSQL):
    'Disease Notification'

    __name__ = 'gnuhealth.disease_notification'
    active = fields.Boolean('Active')
    patient = fields.Many2One('gnuhealth.patient',
                              'Patient',
                              required=True,
                              states=RO_SAVED)
    status = fields.Selection(NOTIFICATION_STATES,
                              'Status',
                              required=True,
                              sort=False)
    status_display = fields.Function(fields.Char('State'),
                                     'get_selection_display')
    name = fields.Char('Code',
                       size=18,
                       states={'readonly': True},
                       required=True)
    tracking_code = fields.Char('Case Tracking Code', select=True)
    date_notified = fields.DateTime('Date reported',
                                    required=True,
                                    states=RO_SAVED)
    date_received = fields.DateTime(
        'Date received',
        states=RO_NEW,
        help='Date received the National Surveillance Unit')
    diagnosis = fields.Many2One('gnuhealth.pathology',
                                'Suspected Diagnosis',
                                states=RO_STATE_END,
                                required=False)
    diagnosis_confirmed = fields.Many2One(
        'gnuhealth.pathology',
        'Confirmed Diagnosis',
        required=False,
        states={'invisible': Eval('id', 0) < 0})
    symptoms = fields.One2Many('gnuhealth.disease_notification.symptom',
                               'name',
                               'Symptoms',
                               states=RO_STATE_END)
    date_onset = fields.Date('Date of Onset',
                             help='Date of onset of the illness')
    epi_week_onset = fields.Function(
        fields.Char('Epi. Week of onset',
                    size=8,
                    help='Week of onset (epidemiological)'), 'epi_week')
    date_seen = fields.Date('Date Seen', help='Date seen by a medical officer')
    reporting_facility = fields.Many2One(
        'gnuhealth.institution',
        'Reporting facility',
        states={'invisible': Bool(Eval('reporting_facility_other'))})
    reporting_facility_other = fields.Char(
        'Other Reporting location',
        help='Used when the report came from an institution not found above',
        states={'invisible': Bool(Eval('reporting_facility'))})
    encounter = fields.Many2One('gnuhealth.encounter',
                                'Clinical Encounter',
                                domain=[('patient', '=', Eval('patient')),
                                        ('start_time', '<',
                                         Eval('date_notified'))])
    specimen_taken = fields.Boolean('Samples Taken')
    specimens = fields.One2Many('gnuhealth.disease_notification.specimen',
                                'notification',
                                'Samples',
                                states=ONLY_IF_LAB)
    hospitalized = fields.Boolean('Admitted to hospital')
    admission_date = fields.Date('Date admitted', states=ONLY_IF_ADMITTED)
    hospital = fields.Many2One('gnuhealth.institution',
                               'Hospital',
                               states=ONLY_IF_ADMITTED)
    ward = fields.Char('Ward', states=ONLY_IF_ADMITTED)
    deceased = fields.Boolean('Deceased')
    date_of_death = fields.Date('Date of Death', states=ONLY_IF_DEAD)
    healthprof = fields.Many2One('gnuhealth.healthprofessional', 'Reported by')
    comments = fields.Text('Additional comments')
    comments_short = fields.Function(fields.Char('Comments'), 'short_comment')
    risk_factors = fields.One2Many(
        'gnuhealth.disease_notification.risk_disease',
        'notification',
        'Risk Factors',
        help="Other conditions of merit")
    hx_travel = fields.Boolean('Recent Foreign Travels',
                               help="History of Overseas travel in the last"
                               " 4 - 6 weeks")
    hx_locations = fields.One2Many(
        'gnuhealth.disease_notification.travel',
        'notification',
        'Places visited',
        states={'invisible': ~Eval('hx_travel', False)})
    age = fields.Function(
        fields.Char('Age', size=8, help='age at date of onset'),
        'get_patient_age')
    sex = fields.Function(fields.Selection(SEX_OPTIONS, 'Sex'),
                          'get_patient_field',
                          searcher='search_patient_field')
    puid = fields.Function(fields.Char('UPI', size=12),
                           'get_patient_field',
                           searcher='search_patient_field')
    state_changes = fields.One2Many(
        'gnuhealth.disease_notification.statechange',
        'notification',
        'Status Changes',
        order=[('create_date', 'DESC')],
        readonly=True)
    ir_received = fields.Boolean('IR Received')
    # medical_record_num = fields.Function(fields.Char('Medical Record Numbers'),
    #                                      'get_patient_field',
    #                                      searcher='search_patient_field')

    @classmethod
    def __setup__(cls):
        super(DiseaseNotification, cls).__setup__()
        cls._order = [('date_onset', 'DESC')]
        cls._sql_error_messages = {
            'unique_name': 'There is another notification with this code'
        }
        cls._sql_constraints = [('name_uniq', 'UNIQUE(name)',
                                 'The code must be unique.')]

    @classmethod
    def get_patient_field(cls, instances, name):
        return dict([(x.id, getattr(x.patient, name)) for x in instances])

    @classmethod
    def get_patient_age(cls, instances, name):
        '''
        Uses the age function in the database to calculate the age at 
        the date specified.
        :param: instance_refs - a list of tuples with (id, ref_date)
        '''
        c = Transaction().cursor
        tbl = cls.__table__()
        qry = "\n".join([
            "SET intervalstyle TO 'iso_8601';",
            "SELECT a.id as id, btrim(lower("
            "regexp_replace(AGE(a.date_onset, c.dob)::varchar, "
            "'([YMD])', '\\1 ', 'g')), 'p ')  as showage ",
            "from " + str(tbl) + " as a ",
            " inner join gnuhealth_patient as b on a.patient=b.id",
            " inner join party_party c on b.name=c.id"
            " where a.id in %s ;"
        ])
        qry_parm = tuple(map(int, instances))
        c.execute(qry, (qry_parm, ))
        outx = c.fetchall()
        outd = dict([x for x in outx])
        return outd

    @classmethod
    def order_puid(cls, tables):
        table, _ = tables[None]
        return [Column(table, 'patient')]
        # not really a UPI/PUID sort, but good enough

    @classmethod
    def short_comment(cls, instances, name):
        return dict(
            map(
                lambda x: (x.id, x.comments and ' '.join(x.comments.split(
                    '\n'))[:40] or ''), instances))

    @classmethod
    def search_patient_field(cls, field_name, clause):
        return replace_clause_column(clause, 'patient.%s' % field_name)

    _rec_name = 'name'
    # @classmethod
    # def get_rec_name(cls, records, name):
    #     return dict([(x.id, x.name) for x in records])

    @classmethod
    def search_rec_name(cls, field_name, clause):
        _, operand, val = clause
        return ['OR', ('patient.puid', operand, val), ('name', operand, val)]

    @fields.depends('reporting_facility')
    def on_change_reporting_facility(self, *arg, **kwarg):
        return {'reporting_facility_other': ''}

    @fields.depends('diagnosis', 'name')
    def on_change_with_name(self):
        curname = self.name
        if self.diagnosis:
            newcode = '%s:' % self.diagnosis.code
            if curname:
                newcode = '%s:%s' % (self.diagnosis.code, curname)
            return newcode
        elif curname and ':' in curname:
            return curname[curname.index(':') + 1:]

    @fields.depends('diagnosis_confirmed', 'status')
    def on_change_diagnosis_confirmed(self):
        if self.diagnosis_confirmed and self.status == 'suspected':
            return {'status': 'confirmed'}
        else:
            return {}

    @fields.depends('encounter')
    def on_change_with_date_seen(self):
        return self.encounter.start_time.date() if self.encounter else None

    @staticmethod
    def default_healthprof():
        healthprof_model = Pool().get('gnuhealth.healthprofessional')
        return healthprof_model.get_health_professional()

    @staticmethod
    def default_status():
        return 'waiting'

    @staticmethod
    def default_active():
        return True

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Config = pool.get('gnuhealth.sequences')
        config = Config(1)
        vlist = [x.copy() for x in vlist]
        for values in vlist:
            val_name = values.get('name', '')
            if not val_name or val_name.endswith(':'):
                newcode = Sequence.get_id(config.notification_sequence.id)
                values['name'] = '%s%s' % (values['name'], newcode)
            elif ':' in val_name and not values.get('diagnosis', False):
                values['name'] = val_name[val_name.index(':') + 1:]
            if values.get('state_changes', False):
                pass
            else:
                values['state_changes'] = [('create', [{
                    'orig_state':
                    None,
                    'target_state':
                    values['status'],
                    'healthprof':
                    values['healthprof']
                }])]
        return super(DiseaseNotification, cls).create(vlist)

    @classmethod
    def write(cls, records, values, *args):
        '''create a NotificationStateChange when the status changes'''
        healthprof = DiseaseNotification.default_healthprof()
        to_make = []
        irecs = iter((records, values) + args)
        for recs, vals in zip(irecs, irecs):
            newstate = vals.get('status', False)
            if newstate:
                for rec in recs:
                    if rec.status != newstate:
                        to_make.append({
                            'notification': rec.id,
                            'orig_state': rec.status,
                            'target_state': newstate,
                            'healthprof': healthprof
                        })
        return_val = super(DiseaseNotification,
                           cls).write(records, values, *args)
        # nsc = Notification State Change
        if to_make:
            nsc = Pool().get('gnuhealth.disease_notification.statechange')
            nsc.create(to_make)
        return return_val

    @classmethod
    def validate(cls, records):
        now = {date: date.today(), datetime: datetime.now(), type(None): None}
        date_fields = [
            'date_onset', 'date_seen', 'date_notified', 'admission_date',
            'date_of_death'
        ]
        # we need to ensure that none of these fields are in the future
        for rec in records:
            if rec.encounter:
                if rec.encounter.patient != rec.patient:
                    cls.raise_user_error('Invalid encounter selected.'
                                         'Different patient')
            for fld in date_fields:
                val = getattr(rec, fld)
                if val and val > now[type(val)]:
                    val_name = getattr(cls, fld).string
                    cls.raise_user_error('%s cannot be in the future',
                                         (val_name, ))

    @classmethod
    def copy(cls, records, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default.update(diagnosis=None, state_changes=[])
        if 'name' in default:
            del default['name']
        return super(DiseaseNotification, cls).copy(records, default=default)

    @classmethod
    def epi_week(cls, instances, name):
        def ewcalc(k):
            return (k.id, epiweek_str(k.date_onset) if k.date_onset else '')

        if name == 'epi_week_onset':
            return dict(map(ewcalc, instances))

    @classmethod
    def get_selection_display(cls, instances, field_name):
        real_field = field_name[:0 - len('_display')]
        field_selections = cls._fields[real_field].selection
        xdict = dict(filter(lambda x: x[0], field_selections))
        return dict(
            map(lambda x: (x.id, xdict.get(getattr(x, real_field), '')),
                instances))
Exemple #18
0
class AccountLiquidationTax(ModelSQL, ModelView):
    'Account Liquidation Tax'
    __name__ = 'account.liquidation.tax'
    _rec_name = 'description'
    liquidation = fields.Many2One('account.liquidation',
                                  'liquidation',
                                  ondelete='CASCADE',
                                  select=True)
    description = fields.Char('Description', size=None, required=True)
    sequence = fields.Integer('Sequence')
    sequence_number = fields.Function(fields.Integer('Sequence Number'),
                                      'get_sequence_number')
    account = fields.Many2One('account.account',
                              'Account',
                              required=True,
                              domain=[
                                  ('kind', '!=', 'view'),
                                  ('company', '=',
                                   Eval('_parent_liquidation',
                                        {}).get('company', 0)),
                              ])
    base = fields.Numeric('Base',
                          required=True,
                          digits=(16, Eval('_parent_liquidation',
                                           {}).get('currency_digits', 2)))
    amount = fields.Numeric('Amount',
                            required=True,
                            digits=(16, Eval('_parent_liquidation',
                                             {}).get('currency_digits', 2)),
                            depends=['tax', 'base', 'manual'])
    manual = fields.Boolean('Manual')
    base_code = fields.Many2One('account.tax.code',
                                'Base Code',
                                domain=[
                                    ('company', '=',
                                     Eval('_parent_liquidation',
                                          {}).get('company', 0)),
                                ])
    base_sign = fields.Numeric('Base Sign', digits=(2, 0), required=True)
    tax_code = fields.Many2One('account.tax.code',
                               'Tax Code',
                               domain=[
                                   ('company', '=',
                                    Eval('_parent_liquidation',
                                         {}).get('company', 0)),
                               ])
    tax_sign = fields.Numeric('Tax Sign', digits=(2, 0), required=True)
    tax = fields.Many2One('account.tax',
                          'Tax',
                          states={
                              'readonly': ~Eval('manual', False),
                          },
                          depends=['manual'])
    tipo = fields.Char('Tipo de retencion')

    @classmethod
    def __setup__(cls):
        super(AccountLiquidationTax, cls).__setup__()
        cls._order.insert(0, ('sequence', 'ASC'))
        cls._error_messages.update({
            'modify': ('You can not modify tax "%(tax)s" from liquidation '
                       '"%(liquidation)s" because it is posted or paid.'),
            'create':
            ('You can not add line "%(line)s" to liquidation '
             '"%(liquidation)s" because it is posted, paid or canceled.'),
            'invalid_account_company':
            ('You can not create liquidation '
             '"%(liquidation)s" on company "%(liquidation_company)s" using '
             'account "%(account)s" from company '
             '"%(account_company)s".'),
            'invalid_base_code_company':
            ('You can not create liquidation '
             '"%(liquidation)s" on company "%(liquidation_company)s" '
             'using base tax code "%(base_code)s" from company '
             '"%(base_code_company)s".'),
            'invalid_tax_code_company':
            ('You can not create liquidation '
             '"%(liquidation)s" on company "%(liquidation_company)s" using tax '
             'code "%(tax_code)s" from company '
             '"%(tax_code_company)s".'),
        })

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        cursor = Transaction().cursor
        table = TableHandler(cursor, cls, module_name)

        super(AccountLiquidationTax, cls).__register__(module_name)

        # Migration from 2.4: drop required on sequence
        table.not_null_action('sequence', action='remove')

    @staticmethod
    def order_sequence(tables):
        table, _ = tables[None]
        return [table.sequence == None, table.sequence]

    @staticmethod
    def default_base():
        return Decimal('0.0')

    @staticmethod
    def default_amount():
        return Decimal('0.0')

    @staticmethod
    def default_manual():
        return True

    @staticmethod
    def default_base_sign():
        return Decimal('1')

    @staticmethod
    def default_tax_sign():
        return Decimal('1')

    @fields.depends('tax', '_parent_liquidation.party',
                    '_parent_liquidation.type')
    def on_change_tax(self):
        Tax = Pool().get('account.tax')
        changes = {}
        if self.tax:
            if self.liquidation:
                context = self.liquidation.get_tax_context()
            else:
                context = {}
            with Transaction().set_context(**context):
                tax = Tax(self.tax.id)
            changes['description'] = tax.description
            if self.liquidation and self.liquidation.type:
                liquidation_type = self.liquidation.type
            else:
                liquidation_type = 'out_liquidation'
            if liquidation_type in ('out_liquidation', 'in_liquidation'):
                changes['base_code'] = (tax.invoice_base_code.id
                                        if tax.invoice_base_code else None)
                changes['base_sign'] = tax.invoice_base_sign
                changes['tax_code'] = (tax.invoice_tax_code.id
                                       if tax.invoice_tax_code else None)
                changes['tax_sign'] = tax.invoice_tax_sign
                changes['account'] = tax.invoice_account.id
        return changes

    @fields.depends('tax', 'base', 'amount', 'manual')
    def on_change_with_amount(self):
        Tax = Pool().get('account.tax')
        transaction = Transaction()
        company = transaction.context['company']
        Company = Pool().get('company.company')
        companies = Company.search([('id', '=', company)])
        for c in companies:
            company = c
        if self.tax and self.manual:

            tax = self.tax
            base = self.base or Decimal(0)
            for values in Tax.compute([tax], base, 1):
                if (values['tax'] == tax and values['base'] == base):
                    amount = company.currency.round(values['amount'])
                    return amount
        return company.currency.round(self.amount)

    @classmethod
    def check_modify(cls, taxes):
        '''
        Check if the taxes can be modified
        '''
        for tax in taxes:
            if tax.liquidation.state in ('posted', 'paid'):
                cls.raise_user_error('modify')

    def get_sequence_number(self, name):
        i = 1
        for tax in self.liquidation.taxes:
            if tax == self:
                return i
            i += 1
        return 0

    @classmethod
    def delete(cls, taxes):
        cls.check_modify(taxes)
        super(AccountLiquidationTax, cls).delete(taxes)

    @classmethod
    def write(cls, *args):
        taxes = sum(args[0::2], [])
        cls.check_modify(taxes)
        super(AccountLiquidationTax, cls).write(*args)

    @classmethod
    def create(cls, vlist):
        Liquidation = Pool().get('account.liquidation')
        liquidation_ids = []
        for vals in vlist:
            if vals.get('Liquidation'):
                liquidation_ids.append(vals['liquidation'])
        for liquidation in Liquidation.browse(liquidation_ids):
            if liquidation.state in ('posted'):
                cls.raise_user_error('create')
        return super(AccountLiquidationTax, cls).create(vlist)

    @classmethod
    def validate(cls, taxes):
        super(AccountLiquidationTax, cls).validate(taxes)
        for tax in taxes:
            tax.check_company()

    def check_company(self):
        company = self.liquidation.company
        if self.account.company != company:
            self.raise_user_error(
                'invalid_account_company', {
                    'liquidation': self.liquidation.rec_name,
                    'liquidation_company': self.liquidation.company.rec_name,
                    'account': self.account.rec_name,
                    'account_company': self.account.company.rec_name,
                })
        if self.base_code:
            if self.base_code.company != company:
                self.raise_user_error(
                    'invalid_base_code_company', {
                        'liquidation': self.liquidation.rec_name,
                        'liquidation_company':
                        self.liquidation.company.rec_name,
                        'base_code': self.base_code.rec_name,
                        'base_code_company': self.base_code.company.rec_name,
                    })
        if self.tax_code:
            if self.tax_code.company != company:
                self.raise_user_error(
                    'invalid_tax_code_company', {
                        'liquidation': self.liquidation.rec_name,
                        'liquidation_company':
                        self.liquidation.company.rec_name,
                        'tax_code': self.tax_code.rec_name,
                        'tax_code_company': self.tax_code.company.rec_name,
                    })

    def get_move_line(self):
        '''
        Return a list of move lines values for liquidation tax
        '''
        Currency = Pool().get('currency.currency')
        res = {}
        if not self.amount:
            return []
        res['description'] = self.description
        if self.liquidation.currency != self.liquidation.company.currency:
            with Transaction().set_context(
                    date=self.liquidation.currency_date):
                amount = Currency.compute(self.liquidation.currency,
                                          self.amount,
                                          self.liquidation.company.currency)
            res['amount_second_currency'] = self.amount
            res['second_currency'] = self.liquidation.currency.id
        else:
            amount = self.amount
            res['amount_second_currency'] = None
            res['second_currency'] = None
        if self.liquidation.type in ('in_liquidation', 'out_credit_note'):
            if amount >= Decimal('0.0'):
                res['debit'] = amount
                res['credit'] = Decimal('0.0')
            else:
                res['debit'] = Decimal('0.0')
                res['credit'] = -amount
                if res['amount_second_currency']:
                    res['amount_second_currency'] = \
                        - res['amount_second_currency']
        else:
            if amount >= Decimal('0.0'):
                res['debit'] = Decimal('0.0')
                res['credit'] = amount
                if res['amount_second_currency']:
                    res['amount_second_currency'] = \
                        - res['amount_second_currency']
            else:
                res['debit'] = -amount
                res['credit'] = Decimal('0.0')
        res['account'] = self.account.id
        if self.account.party_required:
            res['party'] = self.liquidation.party.id
        if self.tax_code:
            res['tax_lines'] = [('create', [{
                'code':
                self.tax_code.id,
                'amount':
                amount * self.tax_sign,
                'tax':
                self.tax and self.tax.id or None
            }])]
        return [res]
Exemple #19
0
class PatientEvaluation(ModelSQL, ModelView):
    __name__ = 'gnuhealth.patient.evaluation'

    serializer = fields.Text('Doc String', readonly=True)

    document_digest = fields.Char('Digest',
                                  readonly=True,
                                  help="Original Document Digest")

    digest_status = fields.Function(fields.Boolean('Altered',
        states={
        'invisible': Not(Equal(Eval('state'),'signed')),
        },
        help="This field will be set whenever parts of" \
        " the main original document has been changed." \
        " Please note that the verification is done only on selected" \
        " fields." ),
        'check_digest')

    serializer_current = fields.Function(
        fields.Text('Current Doc',
                    states={
                        'invisible': Not(Bool(Eval('digest_status'))),
                    }), 'check_digest')

    digest_current = fields.Function(
        fields.Char('Current Hash',
                    states={
                        'invisible': Not(Bool(Eval('digest_status'))),
                    }), 'check_digest')

    digital_signature = fields.Text('Digital Signature', readonly=True)

    @classmethod
    def __setup__(cls):
        super(PatientEvaluation, cls).__setup__()
        cls._buttons.update({
            'sign_evaluation': {
                'invisible': Not(Equal(Eval('state'), 'done')),
            },
        })
        ''' Allow calling the set_signature method via RPC '''
        cls.__rpc__.update({
            'set_signature': RPC(readonly=False),
        })

    @classmethod
    @ModelView.button
    def sign_evaluation(cls, evaluations):
        evaluation = evaluations[0]

        HealthProf = Pool().get('gnuhealth.healthprofessional')

        # Change the state of the evaluation to "Signed"
        # Include signing health professional

        serial_doc = cls.get_serial(evaluation)

        signing_hp = HealthProf.get_health_professional()
        if not signing_hp:
            cls.raise_user_error(
                "No health professional associated to this user !")

        cls.write(
            evaluations, {
                'serializer': serial_doc,
                'document_digest': HealthCrypto().gen_hash(serial_doc),
                'state': 'signed',
            })

    @classmethod
    def get_serial(cls, evaluation):

        signs_symptoms = []
        secondary_conditions = []
        diagnostic_hypotheses = []
        procedures = []

        for sign_symptom in evaluation.signs_and_symptoms:
            finding = []
            finding = [
                sign_symptom.clinical.rec_name,
                sign_symptom.sign_or_symptom,
            ]

            signs_symptoms.append(finding)

        for secondary_condition in evaluation.secondary_conditions:
            sc = []
            sc = [secondary_condition.pathology.rec_name]

            secondary_conditions.append(sc)

        for ddx in evaluation.diagnostic_hypothesis:
            dx = []
            dx = [ddx.pathology.rec_name]

            diagnostic_hypotheses.append(dx)

        for procedure in evaluation.actions:
            proc = []
            proc = [procedure.procedure.rec_name]

            procedures.append(proc)

        data_to_serialize = {
            'Patient':
            str(evaluation.patient.rec_name) or '',
            'Start':
            str(evaluation.evaluation_start) or '',
            'End':
            str(evaluation.evaluation_endtime) or '',
            'Initiated_by':
            str(evaluation.healthprof.rec_name),
            'Signed_by':
            evaluation.signed_by and str(evaluation.signed_by.rec_name) or '',
            'Specialty':
            evaluation.specialty and str(evaluation.specialty.rec_name) or '',
            'Visit_type':
            str(evaluation.visit_type) or '',
            'Urgency':
            str(evaluation.urgency) or '',
            'Information_source':
            str(evaluation.information_source) or '',
            'Reliable_info':
            evaluation.reliable_info,
            'Chief_complaint':
            str(evaluation.chief_complaint) or '',
            'Present_illness':
            str(evaluation.present_illness) or '',
            'Evaluation_summary':
            str(evaluation.evaluation_summary),
            'Signs_and_Symptoms':
            signs_symptoms or '',
            'Glycemia':
            evaluation.glycemia or '',
            'Hba1c':
            evaluation.hba1c or '',
            'Total_Cholesterol':
            evaluation.cholesterol_total or '',
            'HDL':
            evaluation.hdl or '',
            'LDL':
            evaluation.ldl or '',
            'TAG':
            evaluation.ldl or '',
            'Systolic':
            evaluation.systolic or '',
            'Diastolic':
            evaluation.diastolic or '',
            'BPM':
            evaluation.bpm or '',
            'Respiratory_rate':
            evaluation.respiratory_rate or '',
            'Osat':
            evaluation.osat or '',
            'BPM':
            evaluation.bpm or '',
            'Malnutrition':
            evaluation.malnutrition,
            'Dehydration':
            evaluation.dehydration,
            'Temperature':
            evaluation.temperature,
            'Weight':
            evaluation.weight or '',
            'Height':
            evaluation.height or '',
            'BMI':
            evaluation.bmi or '',
            'Head_circ':
            evaluation.head_circumference or '',
            'Abdominal_cir':
            evaluation.abdominal_circ or '',
            'Hip':
            evaluation.hip or '',
            'WHR':
            evaluation.whr or '',
            'Abdominal_cir':
            evaluation.abdominal_circ or '',
            'Loc':
            evaluation.loc or '',
            'Loc_eyes':
            evaluation.loc_eyes or '',
            'Loc_verbal':
            evaluation.loc_verbal or '',
            'Loc_motor':
            evaluation.loc_motor or '',
            'Tremor':
            evaluation.tremor,
            'Violent':
            evaluation.violent,
            'Mood':
            str(evaluation.mood) or '',
            'Orientation':
            evaluation.orientation,
            'Orientation':
            evaluation.orientation,
            'Memory':
            evaluation.memory,
            'Knowledge_current_events':
            evaluation.knowledge_current_events,
            'Judgment':
            evaluation.judgment,
            'Abstraction':
            evaluation.abstraction,
            'Vocabulary':
            evaluation.vocabulary,
            'Calculation':
            evaluation.calculation_ability,
            'Object_recognition':
            evaluation.object_recognition,
            'Praxis':
            evaluation.praxis,
            'Diagnosis':
            evaluation.diagnosis and str(evaluation.diagnosis.rec_name) or '',
            'Secondary_conditions':
            secondary_conditions or '',
            'DDX':
            diagnostic_hypotheses or '',
            'Info_Diagnosis':
            str(evaluation.info_diagnosis) or '',
            'Treatment_plan':
            str(evaluation.directions) or '',
            'Procedures':
            procedures or '',
            'Institution':
            evaluation.institution and str(evaluation.institution.rec_name)
            or '',
            'Derived_from':
            evaluation.derived_from and str(evaluation.derived_from.rec_name)
            or '',
            'Derived_to':
            evaluation.derived_to and str(evaluation.derived_to.rec_name)
            or '',
        }

        serialized_doc = str(HealthCrypto().serialize(data_to_serialize))

        return serialized_doc

    @classmethod
    def set_signature(cls, data, signature):
        """
        Set the clearsigned signature
        """
        doc_id = data['id']

        cls.write([cls(doc_id)], {
            'digital_signature': signature,
        })

    def check_digest(self, name):
        result = ''
        serial_doc = str(self.get_serial(self))
        if (name == 'digest_status' and self.document_digest):
            if (HealthCrypto().gen_hash(serial_doc) == self.document_digest):
                result = False
            else:
                ''' Return true if the document has been altered'''
                result = True
        if (name == 'digest_current'):
            result = HealthCrypto().gen_hash(serial_doc)

        if (name == 'serializer_current'):
            result = serial_doc

        return result

    # Hide the group holding all the digital signature until signed

    @classmethod
    def view_attributes(cls):
        return [('//group[@id="group_digital_signature"]', 'states', {
            'invisible': ~Eval('digital_signature')
        }),
                ('//group[@id="group_current_string"]', 'states', {
                    'invisible': ~Eval('digest_status'),
                })]
Exemple #20
0
class MoveLine:
    __name__ = 'account.move.line'
    payment_amount = fields.Function(fields.Numeric(
        'Payment Amount',
        digits=(16,
                If(Bool(Eval('second_currency_digits')),
                   Eval('second_currency_digits', 2),
                   Eval('currency_digits', 2))),
        states={
            'invisible': ~Eval('payment_kind'),
        },
        depends=['payment_kind', 'second_currency_digits', 'currency_digits']),
                                     'get_payment_amount',
                                     searcher='search_payment_amount')
    payments = fields.One2Many('account.payment',
                               'line',
                               'Payments',
                               readonly=True,
                               states={
                                   'invisible': ~Eval('payment_kind'),
                               },
                               depends=['payment_kind'])
    payment_kind = fields.Function(fields.Selection([
        (None, ''),
    ] + KINDS, 'Payment Kind'),
                                   'get_payment_kind',
                                   searcher='search_payment_kind')

    @classmethod
    def __setup__(cls):
        super(MoveLine, cls).__setup__()
        cls._buttons.update({
            'pay': {
                'invisible': ~Eval('payment_kind').in_(dict(KINDS).keys()),
            },
        })

    @classmethod
    def get_payment_amount(cls, lines, name):
        amounts = {}
        for line in lines:
            if line.account.kind not in ('payable', 'receivable'):
                amounts[line.id] = None
                continue
            if line.second_currency:
                amount = abs(line.amount_second_currency)
            else:
                amount = abs(line.credit - line.debit)

            for payment in line.payments:
                if payment.state != 'failed':
                    amount -= payment.amount

            amounts[line.id] = amount
        return amounts

    @classmethod
    def search_payment_amount(cls, name, clause):
        pool = Pool()
        Payment = pool.get('account.payment')
        Account = pool.get('account.account')
        _, operator, value = clause
        Operator = fields.SQL_OPERATORS[operator]
        table = cls.__table__()
        payment = Payment.__table__()
        account = Account.__table__()

        payment_amount = Sum(Coalesce(payment.amount, 0))
        main_amount = Abs(table.credit - table.debit) - payment_amount
        second_amount = Abs(table.amount_second_currency) - payment_amount
        amount = Case((table.second_currency == Null, main_amount),
                      else_=second_amount)
        value = cls.payment_amount.sql_format(value)

        query = table.join(
            payment,
            type_='LEFT',
            condition=(table.id == payment.line) &
            (payment.state != 'failed')).join(
                account, condition=table.account == account.id).select(
                    table.id,
                    where=account.kind.in_(['payable', 'receivable']),
                    group_by=(table.id, account.kind, table.second_currency),
                    having=Operator(amount, value))
        return [('id', 'in', query)]

    def get_payment_kind(self, name):
        return self.account.kind if self.account.kind in dict(KINDS) else None

    @classmethod
    def search_payment_kind(cls, name, clause):
        return [('account.kind', ) + tuple(clause[1:])]

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

    @classmethod
    @ModelView.button_action('account_payment.act_pay_line')
    def pay(cls, lines):
        pass
Exemple #21
0
class User(ModelSQL, ModelView):
    'Web User'
    __name__ = 'web.user'
    _rec_name = 'email'

    email = fields.Char('E-mail', required=True, select=True)
    email_valid = fields.Boolean('E-mail Valid')
    email_token = fields.Char('E-mail Token', select=True)
    password_hash = fields.Char('Password Hash')
    password = fields.Function(fields.Char('Password'),
                               'get_password',
                               setter='set_password')
    reset_password_token = fields.Char('Reset Password Token', select=True)
    reset_password_token_expire = fields.Timestamp(
        'Reset Password Token Expire')
    active = fields.Boolean('Active')
    party = fields.Many2One('party.party', 'Party')

    @classmethod
    def __setup__(cls):
        super(User, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints += [
            ('email_unique', Unique(table,
                                    table.email), 'E-mail must be unique'),
        ]
        cls._buttons.update({
            'validate_email': {
                'readonly': Eval('email_valid', False),
            },
            'reset_password': {
                'readonly': ~Eval('email_valid', False),
            },
        })

    @staticmethod
    def default_active():
        return True

    @classmethod
    def default_email_valid(cls):
        return False

    def get_password(self, name):
        return 'x' * 10

    @classmethod
    def set_password(cls, users, name, value):
        pool = Pool()
        User = pool.get('res.user')

        if value == 'x' * 10:
            return

        if Transaction().user and value:
            User.validate_password(value, users)

        to_write = []
        for user in users:
            to_write.extend([[user], {
                'password_hash': cls.hash_password(value),
            }])
        cls.write(*to_write)

    @classmethod
    def _format_email(cls, users):
        for user in users:
            email = user.email.lower()
            if email != user.email:
                user.email = email
        cls.save(users)

    @classmethod
    def create(cls, vlist):
        users = super(User, cls).create(vlist)
        cls._format_email(users)
        return users

    @classmethod
    def write(cls, *args):
        super(User, cls).write(*args)
        users = sum(args[0:None:2], [])
        cls._format_email(users)

    @classmethod
    def authenticate(cls, email, password):
        pool = Pool()
        Attempt = pool.get('web.user.authenticate.attempt')
        email = email.lower()

        # Prevent brute force attack
        Transaction().atexit(time.sleep, 2**Attempt.count(email) - 1)

        users = cls.search([('email', '=', email)])
        if users:
            user, = users
            if cls.check_password(password, user.password_hash):
                Attempt.remove(email)
                return user
        Attempt.add(email)

    @staticmethod
    def hash_method():
        return 'bcrypt' if bcrypt else 'sha1'

    @classmethod
    def hash_password(cls, password):
        '''Hash given password in the form
        <hash_method>$<password>$<salt>...'''
        if not password:
            return ''
        return getattr(cls, 'hash_' + cls.hash_method())(password)

    @classmethod
    def check_password(cls, password, hash_):
        if not hash_:
            return False
        hash_method = hash_.split('$', 1)[0]
        return getattr(cls, 'check_' + hash_method)(password, hash_)

    @classmethod
    def hash_sha1(cls, password):
        salt = ''.join(random.sample(string.ascii_letters + string.digits, 8))
        salted_password = password + salt
        if isinstance(salted_password, unicode):
            salted_password = salted_password.encode('utf-8')
        hash_ = hashlib.sha1(salted_password).hexdigest()
        return '$'.join(['sha1', hash_, salt])

    @classmethod
    def check_sha1(cls, password, hash_):
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        hash_method, hash_, salt = hash_.split('$', 2)
        salt = salt or ''
        if isinstance(salt, unicode):
            salt = salt.encode('utf-8')
        assert hash_method == 'sha1'
        return hash_ == hashlib.sha1(password + salt).hexdigest()

    @classmethod
    def hash_bcrypt(cls, password):
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        hash_ = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8')
        return '$'.join(['bcrypt', hash_])

    @classmethod
    def check_bcrypt(cls, password, hash_):
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        hash_method, hash_ = hash_.split('$', 1)
        if isinstance(hash_, unicode):
            hash_ = hash_.encode('utf-8')
        assert hash_method == 'bcrypt'
        return hash_ == bcrypt.hashpw(password, hash_)

    def new_session(self):
        pool = Pool()
        Session = pool.get('web.user.session')
        return Session.add(self)

    @classmethod
    def get_user(cls, session):
        pool = Pool()
        Session = pool.get('web.user.session')
        return Session.get_user(session)

    @classmethod
    @ModelView.button
    def validate_email(cls, users, from_=None):
        for user in users:
            user.set_email_token()
        cls.save(users)
        _send_email(from_, users, cls.get_email_validation)

    def set_email_token(self, nbytes=None):
        self.email_token = token_hex(nbytes)

    def get_email_validation(self):
        return get_email('web.user.email_validation', self, self.languages)

    def get_email_validation_url(self, url=None):
        if url is None:
            url = config.get('web', 'email_validation_url')
        return _add_params(url, token=self.email_token)

    @classmethod
    def validate_email_url(cls, url):
        parts = urlparse.urlsplit(url)
        tokens = filter(None,
                        urlparse.parse_qs(parts.query).get('token', [None]))
        return cls.validate_email_token(tokens)

    @classmethod
    def validate_email_token(cls, tokens):
        users = cls.search([
            ('email_token', 'in', tokens),
        ])
        cls.write(users, {
            'email_valid': True,
            'email_token': None,
        })
        return bool(users)

    @classmethod
    @ModelView.button
    def reset_password(cls, users, from_=None):
        now = datetime.datetime.now()

        # Prevent abusive reset
        def reset(user):
            return not (user.reset_password_token_expire
                        and user.reset_password_token_expire > now)

        users = filter(reset, users)

        for user in users:
            user.set_reset_password_token()
        cls.save(users)
        _send_email(from_, users, cls.get_email_reset_password)

    def set_reset_password_token(self, nbytes=None):
        self.reset_password_token = token_hex(nbytes)
        self.reset_password_token_expire = (
            datetime.datetime.now() + datetime.timedelta(seconds=config.getint(
                'session', 'web_timeout_reset', default=24 * 60 * 60)))

    def clear_reset_password_token(self):
        self.reset_password_token = None
        self.reset_password_token_expire = None

    def get_email_reset_password(self):
        return get_email('web.user.email_reset_password', self, self.languages)

    def get_email_reset_password_url(self, url=None):
        if url is None:
            url = config.get('web', 'reset_password_url')
        return _add_params(url,
                           token=self.reset_password_token,
                           email=self.email)

    @classmethod
    def set_password_url(cls, url, password):
        parts = urlparse.urlsplit(url)
        query = urlparse.parse_qs(parts.query)
        email = query.get('email', [None])[0]
        token = query.get('token', [None])[0]
        return cls.set_password_token(email, token, password)

    @classmethod
    def set_password_token(cls, email, token, password):
        pool = Pool()
        Attempt = pool.get('web.user.authenticate.attempt')
        email = email.lower()

        # Prevent brute force attack
        Transaction().atexit(time.sleep, 2**Attempt.count(email) - 1)

        users = cls.search([
            ('email', '=', email),
        ])
        if users:
            user, = users
            if user.reset_password_token == token:
                now = datetime.datetime.now()
                expire = user.reset_password_token_expire
                user.clear_reset_password_token()
                if expire > now:
                    user.password = password
                    user.save()
                    Attempt.remove(email)
                    return True
        Attempt.add(email)
        return False

    @property
    def languages(self):
        pool = Pool()
        Language = pool.get('ir.lang')
        if self.party and self.party.lang:
            languages = [self.party.lang]
        else:
            languages = Language.search([
                ('code', '=', Transaction().language),
            ])
        return languages
Exemple #22
0
class EmailTemplate(ModelSQL, ModelView):
    "Email Template"
    __name__ = 'ir.email.template'

    model = fields.Many2One('ir.model', "Model", required=True)
    name = fields.Char("Name", required=True, translate=True)
    recipients = fields.Many2One(
        'ir.model.field',
        "Recipients",
        states={
            'invisible': Bool(Eval('recipients_pyson')),
        },
        depends=['recipients_pyson'],
        help="The field that contains the recipient(s).")
    recipients_pyson = fields.Char(
        "Recipients",
        states={
            'invisible': Bool(Eval('recipients')),
        },
        depends=['recipients'],
        help="A PYSON expression that generates a list of recipients "
        'with the record represented by "self".')
    recipients_secondary = fields.Many2One(
        'ir.model.field',
        "Secondary Recipients",
        states={
            'invisible': Bool(Eval('recipients_secondary_pyson')),
        },
        depends=['recipients_secondary_pyson'],
        help="The field that contains the secondary recipient(s).")
    recipients_secondary_pyson = fields.Char(
        "Secondary Recipients",
        states={
            'invisible': Bool(Eval('recipients_secondary')),
        },
        depends=['recipients_secondary'],
        help="A PYSON expression that generates a list "
        'of secondary recipients with the record represented by "self".')
    recipients_hidden = fields.Many2One(
        'ir.model.field',
        "Hidden Recipients",
        states={
            'invisible': Bool(Eval('recipients_hidden_pyson')),
        },
        depends=['recipients_hidden_pyson'],
        help="The field that contains the secondary recipient(s).")
    recipients_hidden_pyson = fields.Char(
        "Hidden Recipients",
        states={
            'invisible': Bool(Eval('recipients_hidden')),
        },
        depends=['recipients_hidden'],
        help="A PYSON expression that generates a list of hidden recipients "
        'with the record represented by "self".')
    subject = fields.Char("Subject", translate=True)
    body = fields.Text("Body", translate=True)
    reports = fields.Many2Many('ir.email.template-ir.action.report',
                               'template',
                               'report',
                               "Reports",
                               domain=[
                                   ('model', '=', Eval('model_name')),
                               ],
                               depends=['model_name'])

    model_name = fields.Function(fields.Char("Model Name"),
                                 'on_change_with_model_name')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        for field in [
                'recipients',
                'recipients_secondary',
                'recipients_hidden',
        ]:
            field = getattr(cls, field)
            field.domain = [('model', '=', Eval('model')),
                            [
                                'OR',
                                ('relation', 'in', cls.email_models()),
                                [
                                    ('model.model', 'in', cls.email_models()),
                                    ('name', '=', 'id'),
                                ],
                            ]]
            field.depends.add('model')
        cls.__rpc__.update({
            'get': RPC(instantiate=0),
            'get_default': RPC(),
        })

    @fields.depends('model')
    def on_change_with_model_name(self, name=None):
        if self.model:
            return self.model.model

    @classmethod
    def validate_fields(cls, templates, field_names):
        super().validate_fields(templates, field_names)
        cls.check_subject(templates, field_names)
        cls.check_body(templates, field_names)
        cls.check_fields_pyson(templates, field_names)

    @classmethod
    def check_subject(cls, templates, field_names=None):
        if field_names and 'subject' not in field_names:
            return
        for template in templates:
            if not template.subject:
                continue
            try:
                TextTemplate(template.subject)
            except Exception as exception:
                raise EmailTemplateError(
                    gettext('ir.msg_email_template_invalid_subject',
                            template=template.rec_name,
                            exception=exception)) from exception

    @classmethod
    def check_body(self, templates, field_names=None):
        if field_names and 'body' not in field_names:
            return
        for template in templates:
            if not template.body:
                continue
            try:
                TextTemplate(template.body)
            except Exception as exception:
                raise EmailTemplateError(
                    gettext('ir.msg_email_template_invalid_body',
                            template=template.rec_name,
                            exception=exception)) from exception

    @classmethod
    def check_fields_pyson(cls, templates, field_names=None):
        pyson_fields = {
            'recipients_pyson',
            'recipients_secondary_pyson',
            'recipients_hidden_pyson',
        }
        if field_names:
            pyson_fields &= field_names
        if not pyson_fields:
            return
        encoder = PYSONDecoder(noeval=True)
        for template in templates:
            for field in pyson_fields:
                value = getattr(template, field)
                if not value:
                    continue
                try:
                    pyson = encoder.decode(value)
                except Exception as exception:
                    raise EmailTemplateError(
                        gettext('ir.msg_email_template_invalid_field_pyson',
                                template=template.rec_name,
                                field=cls.__names__(field)['field'],
                                exception=exception)) from exception
                if not isinstance(pyson, list) and pyson.types() != {list}:
                    raise EmailTemplateError(
                        gettext(
                            'ir.msg_email_template_invalid_field_pyson_type',
                            template=template.rec_name,
                            field=cls.__names__(field)['field'],
                        ))

    def get(self, record):
        pool = Pool()
        Model = pool.get(self.model.model)
        record = Model(int(record))

        values = {}
        for attr, key in [
            ('recipients', 'to'),
            ('recipients_secondary', 'cc'),
            ('recipients_hidden', 'bcc'),
        ]:
            field = getattr(self, attr)
            try:
                if field:
                    if field.name == 'id':
                        value = record
                    else:
                        value = getattr(record, field.name, None)
                    if value:
                        values[key] = self.get_addresses(value)
                else:
                    value = getattr(self, attr + '_pyson')
                    if value:
                        value = self.eval(record, value)
                        if value:
                            values[key] = self.get_addresses(value)
            except AccessError:
                continue

        if self.subject:
            try:
                values['subject'] = (TextTemplate(self.subject).generate(
                    **self.get_context(record)).render())
            except AccessError:
                pass
        if self.body:
            try:
                values['body'] = (TextTemplate(
                    self.body).generate(**self.get_context(record)).render())
            except AccessError:
                pass
        if self.reports:
            values['reports'] = [r.id for r in self.reports]
        return values

    def get_context(self, record):
        pool = Pool()
        User = pool.get('res.user')
        return {
            'context': Transaction().context,
            'user': User(Transaction().user),
            'record': record,
            'format_date': Report.format_date,
            'format_datetime': Report.format_datetime,
            'format_timedelta': Report.format_timedelta,
            'format_currency': Report.format_currency,
            'format_number': Report.format_number,
        }

    def eval(self, record, pyson, _env=None):
        'Evaluate the pyson with the record'
        if _env is None:
            env = {}
        else:
            env = _env.copy()
        env['context'] = Transaction().context
        env['self'] = EvalEnvironment(record, record.__class__)
        return PYSONDecoder(env).decode(pyson)

    @classmethod
    def _get_default_exclude(cls, record):
        return ['create_uid', 'write_uid']

    @classmethod
    def get_default(cls, model, record):
        pool = Pool()
        Field = pool.get('ir.model.field')
        Model = pool.get(model)
        record = Model(int(record))
        values = {}

        fields = Field.search([
            ('model.model', '=', model),
            ('name', 'not in', cls._get_default_exclude(record)),
            [
                'OR',
                ('relation', 'in', cls.email_models()),
                [
                    ('model.model', 'in', cls.email_models()),
                    ('name', '=', 'id'),
                ],
            ],
        ])
        addresses = set()
        for field in fields:
            try:
                if field.name == 'id':
                    value = record
                else:
                    value = getattr(record, field.name)
                addresses.update(cls.get_addresses(value))
            except AccessError:
                pass
        values['to'] = list(addresses)

        try:
            values['subject'] = '%s: %s' % (Model.__names__()['model'],
                                            record.rec_name)
        except AccessError:
            pass
        return values

    @classmethod
    def email_models(cls):
        return ['res.user']

    @classmethod
    def get_addresses(cls, value):
        if isinstance(value, (list, tuple)):
            addresses = (cls._get_address(v) for v in value)
        else:
            addresses = [cls._get_address(value)]
        return [
            _formataddr((name, email))
            for name, email in filter(None, addresses) if email
        ]

    @classmethod
    def _get_address(cls, record):
        pool = Pool()
        User = pool.get('res.user')
        if isinstance(record, str):
            return (None, record)
        elif isinstance(record, User) and record.email:
            return (record.name, record.email)

    @classmethod
    def get_languages(cls, value):
        pool = Pool()
        Configuration = pool.get('ir.configuration')
        Lang = pool.get('ir.lang')
        if isinstance(value, (list, tuple)):
            languagues = {cls._get_language(v) for v in value}
        else:
            languagues = {cls._get_language(value)}
        languagues = list(filter(None, languagues))
        if not languagues:
            return Lang.search([
                ('code', '=', Configuration.get_language()),
            ],
                               limit=1)
        return languagues

    @classmethod
    def _get_language(cls, record):
        pool = Pool()
        User = pool.get('res.user')
        if isinstance(record, User) and record.language:
            return record.language
Exemple #23
0
class Category(CompanyMultiValueMixin, metaclass=PoolMeta):
    __name__ = 'product.category'
    accounting = fields.Boolean('Accounting', select=True,
        states={
            'readonly': Bool(Eval('childs', [0])) | Bool(Eval('parent')),
            },
        depends=['parent'],
        help="Check to convert into accounting category.")
    account_parent = fields.Boolean('Use Parent\'s accounts',
        states={
            'invisible': ~Eval('accounting', False),
            },
        depends=['accounting'],
        help="Use the accounts defined on the parent category.")
    accounts = fields.One2Many(
        'product.category.account', 'category', "Accounts")
    account_expense = fields.MultiValue(fields.Many2One('account.account',
            'Account Expense', domain=[
                ('kind', '=', 'expense'),
                ('company', '=', Eval('context', {}).get('company', -1)),
                ],
            states={
                'invisible': (~Eval('context', {}).get('company')
                    | Eval('account_parent')
                    | ~Eval('accounting', False)),
                },
            depends=['account_parent', 'accounting']))
    account_revenue = fields.MultiValue(fields.Many2One('account.account',
            'Account Revenue', domain=[
                ('kind', '=', 'revenue'),
                ('company', '=', Eval('context', {}).get('company', -1)),
                ],
            states={
                'invisible': (~Eval('context', {}).get('company')
                    | Eval('account_parent')
                    | ~Eval('accounting', False)),
                },
            depends=['account_parent', 'accounting']))
    taxes_parent = fields.Boolean('Use the Parent\'s Taxes',
        states={
            'invisible': ~Eval('accounting', False),
            },
        depends=['accounting'],
        help="Use the taxes defined on the parent category.")
    customer_taxes = fields.Many2Many('product.category-customer-account.tax',
        'category', 'tax', 'Customer Taxes',
        order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')],
        domain=[('parent', '=', None), ['OR',
                ('group', '=', None),
                ('group.kind', 'in', ['sale', 'both'])],
            ],
        states={
            'invisible': (~Eval('context', {}).get('company')
                | Eval('taxes_parent')
                | ~Eval('accounting', False)),
            },
        depends=['taxes_parent', 'accounting'],
        help="The taxes to apply when selling products of this category.")
    supplier_taxes = fields.Many2Many('product.category-supplier-account.tax',
        'category', 'tax', 'Supplier Taxes',
        order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')],
        domain=[('parent', '=', None), ['OR',
                ('group', '=', None),
                ('group.kind', 'in', ['purchase', 'both'])],
            ],
        states={
            'invisible': (~Eval('context', {}).get('company')
                | Eval('taxes_parent')
                | ~Eval('accounting', False)),
            },
        depends=['taxes_parent', 'accounting'],
        help="The taxes to apply when purchasing products of this category.")
    customer_taxes_used = fields.Function(fields.One2Many('account.tax', None,
            'Customer Taxes Used'), 'get_taxes')
    supplier_taxes_used = fields.Function(fields.One2Many('account.tax', None,
            'Supplier Taxes Used'), 'get_taxes')

    @classmethod
    def __setup__(cls):
        super(Category, cls).__setup__()
        cls._error_messages.update({
            'missing_account': ('There is no '
                    '"%(field)s" defined on the category "%(name)s"'),
            })
        cls.parent.domain = [
            ('accounting', '=', Eval('accounting', False)),
            cls.parent.domain or []]
        cls.parent.depends.append('accounting')
        cls.parent.states['required'] = Or(
            cls.parent.states.get('required', False),
            Eval('account_parent', False) | Eval('taxes_parent', False))
        cls.parent.depends.extend(['account_parent', 'taxes_parent'])

    @classmethod
    def multivalue_model(cls, field):
        pool = Pool()
        if field in {'account_expense', 'account_revenue'}:
            return pool.get('product.category.account')
        return super(Category, cls).multivalue_model(field)

    @classmethod
    def default_accounting(cls):
        return False

    @classmethod
    def default_account_expense(cls, **pattern):
        pool = Pool()
        Configuration = pool.get('account.configuration')
        config = Configuration(1)
        account = config.get_multivalue(
            'default_category_account_expense', **pattern)
        return account.id if account else None

    @classmethod
    def default_account_revenue(cls, **pattern):
        pool = Pool()
        Configuration = pool.get('account.configuration')
        config = Configuration(1)
        account = config.get_multivalue(
            'default_category_account_revenue', **pattern)
        return account.id if account else None

    def get_account(self, name, **pattern):
        if self.account_parent:
            return self.parent.get_account(name, **pattern)
        else:
            return self.get_multivalue(name[:-5], **pattern)

    def get_taxes(self, name):
        if self.taxes_parent:
            return [x.id for x in getattr(self.parent, name)]
        else:
            return [x.id for x in getattr(self, name[:-5])]

    @fields.depends('parent', 'accounting')
    def on_change_with_accounting(self):
        if self.parent:
            return self.parent.accounting
        return self.accounting

    @fields.depends('account_expense')
    def on_change_account_expense(self):
        if self.account_expense:
            self.supplier_taxes = self.account_expense.taxes
        else:
            self.supplier_taxes = []

    @fields.depends('account_revenue')
    def on_change_account_revenue(self):
        if self.account_revenue:
            self.customer_taxes = self.account_revenue.taxes
        else:
            self.customer_taxes = []

    @classmethod
    def view_attributes(cls):
        return super(Category, cls).view_attributes() + [
            ('/form/notebook/page[@id="accounting"]', 'states', {
                    'invisible': ~Eval('accounting', False),
                    }),
            ]

    @property
    @account_used('account_expense')
    def account_expense_used(self):
        pass

    @property
    @account_used('account_revenue')
    def account_revenue_used(self):
        pass
Exemple #24
0
class Email(ResourceAccessMixin, ModelSQL, ModelView):
    "Email"
    __name__ = 'ir.email'

    user = fields.Function(fields.Char("User"), 'get_user')
    at = fields.Function(fields.DateTime("At"), 'get_at')
    recipients = fields.Char("Recipients", readonly=True)
    recipients_secondary = fields.Char("Secondary Recipients", readonly=True)
    recipients_hidden = fields.Char("Hidden Recipients", readonly=True)
    addresses = fields.One2Many('ir.email.address',
                                'email',
                                "Addresses",
                                readonly=True)
    subject = fields.Char("Subject", readonly=True)
    body = fields.Text("Body", readonly=True)

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._order.insert(0, ('create_date', 'DESC'))
        cls.__rpc__.update({
            'send': RPC(readonly=False, result=int),
            'complete': RPC(check_access=False),
        })
        del cls.__rpc__['create']

    def get_user(self, name):
        return self.create_uid.rec_name

    def get_at(self, name):
        return self.create_date.replace(microsecond=0)

    @classmethod
    def send(cls,
             to='',
             cc='',
             bcc='',
             subject='',
             body='',
             files=None,
             record=None,
             reports=None,
             attachments=None):
        pool = Pool()
        User = pool.get('res.user')
        ActionReport = pool.get('ir.action.report')
        Attachment = pool.get('ir.attachment')
        transaction = Transaction()
        user = User(transaction.user)

        Model = pool.get(record[0])
        record = Model(record[1])

        body_html = HTML_EMAIL % {
            'subject': subject,
            'body': body,
            'signature': user.signature or '',
        }
        content = MIMEMultipart('alternative')
        if html2text:
            body_text = HTML_EMAIL % {
                'subject': subject,
                'body': body,
                'signature': '',
            }
            converter = html2text.HTML2Text()
            body_text = converter.handle(body_text)
            if user.signature:
                body_text += '\n-- \n' + converter.handle(user.signature)
            part = MIMEText(body_text, 'plain', _charset='utf-8')
            content.attach(part)
        part = MIMEText(body_html, 'html', _charset='utf-8')
        content.attach(part)
        if files or reports or attachments:
            msg = MIMEMultipart('mixed')
            msg.attach(content)
            if files is None:
                files = []
            else:
                files = list(files)

            for report_id in (reports or []):
                report = ActionReport(report_id)
                Report = pool.get(report.report_name, type='report')
                ext, content, _, title = Report.execute([record.id], {
                    'action_id': report.id,
                })
                name = '%s.%s' % (title, ext)
                if isinstance(content, str):
                    content = content.encode('utf-8')
                files.append((name, content))
            if attachments:
                files += [(a.name, a.data)
                          for a in Attachment.browse(attachments)]
            for name, data in files:
                mimetype, _ = mimetypes.guess_type(name)
                if mimetype:
                    attachment = MIMENonMultipart(*mimetype.split('/'))
                    attachment.set_payload(data)
                    encode_base64(attachment)
                else:
                    attachment = MIMEApplication(data)
                attachment.add_header('Content-Disposition',
                                      'attachment',
                                      filename=('utf-8', '', name))
                msg.attach(attachment)
        else:
            msg = content
        from_ = config.get('email', 'from')
        set_from_header(msg, from_, user.email or from_)
        msg['To'] = ', '.join(formataddr(a) for a in getaddresses([to]))
        msg['Cc'] = ', '.join(formataddr(a) for a in getaddresses([cc]))
        msg['Subject'] = Header(subject, 'utf-8')

        to_addrs = list(
            filter(
                None,
                map(str.strip,
                    _get_emails(to) + _get_emails(cc) + _get_emails(bcc))))
        sendmail_transactional(from_,
                               to_addrs,
                               msg,
                               datamanager=SMTPDataManager(strict=True))

        email = cls(recipients=to,
                    recipients_secondary=cc,
                    recipients_hidden=bcc,
                    addresses=[{
                        'address': a
                    } for a in to_addrs],
                    subject=subject,
                    body=body,
                    resource=record)
        email.save()
        with Transaction().set_context(_check_access=False):
            attachments_ = []
            for name, data in files:
                attachments_.append(
                    Attachment(resource=email, name=name, data=data))
            Attachment.save(attachments_)
        return email

    @classmethod
    def complete(cls, text, limit):
        limit = int(limit)
        if not limit > 0:
            raise ValueError('limit must be > 0: %r' % (limit, ))
        emails = getaddresses([text])
        if not emails:
            return []
        name, email = map(str.strip, emails[-1])
        if not name and not email:
            return []
        s = StringMatcher()
        try:
            s.set_seq2(_formataddr((name, email)))
        except UnicodeEncodeError:
            return []

        def generate(name, email):
            for name, email in cls._match(name, email):
                try:
                    address = _formataddr((name, email))
                except UnicodeEncodeError:
                    continue
                s.set_seq1(address)
                yield (s.ratio(), address, ', '.join(
                    map(_formataddr, emails[:-1] + [(name, email)])))

        return heapq.nlargest(limit, generate(name, email))

    @classmethod
    def _match(cls, name, email):
        pool = Pool()
        User = pool.get('res.user')
        domain = ['OR']
        for field in ['name', 'login', 'email']:
            for value in [name, email]:
                if value and len(value) >= 3:
                    domain.append(
                        (field, 'ilike', '%' + escape_wildcard(value) + '%'))
        for user in User.search([
            ('email', '!=', ''),
                domain,
        ], order=[]):
            yield user.name, user.email
Exemple #25
0
class Subscription(ModelSQL, ModelView):
    "Subscription"
    __name__ = 'sale.subscription'

    student = fields.Many2One('party.party',
                              "Student",
                              required=True,
                              states={
                                  'readonly': Eval('state') != 'draft',
                              },
                              depends=['state', 'company'],
                              domain=[
                                  'AND',
                                  [('is_student', '=', True)],
                                  [('company', '=', Eval('company', -1))],
                              ],
                              help="The student who subscribe.")

    origin = fields.Reference('Origin',
                              selection='get_origin',
                              select=True,
                              states={
                                  'readonly': Eval('state') != 'draft',
                              },
                              depends=['state'])

    invoices = fields.One2Many('account.invoice', 'subscription_origin',
                               'Invoices')

    amount = fields.Function(
        fields.Numeric(
            'Total',
            digits=(16, Eval('currency_digits', 2)),
        ), 'get_amount')

    registration_date = fields.Date('Fecha de inscripcion',
                                    required=True,
                                    states={
                                        'readonly': Eval('state') != 'draft',
                                    },
                                    depends=['state'])

    section = fields.Char('Section',
                          required=False,
                          states={
                              'readonly': Eval('state') != 'draft',
                          },
                          depends=['state'])

    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'get_currency_digits')

    receivable = fields.Function(
        fields.Numeric(
            'Receivable',
            digits=(16, 2),
        ), 'get_receivable_payable')

    enrolment = fields.Many2One('product.product',
                                'Enrolment',
                                domain=[
                                    'AND',
                                    [('is_enrolment', '=', True)],
                                    [('company', '=', Eval('company', -1))],
                                ],
                                required=True,
                                states={
                                    'readonly': Eval('state') != 'draft',
                                },
                                depends=['state', 'company'])

    unit_price_enrolment = fields.Numeric(
        "Enrolment Price",
        digits=(16, 2),
        required=True,
        states={
            'readonly': Eval('state') != 'draft',
        },
        depends=['state'],
    )

    grade = fields.Function(fields.Many2One('sale.subscription.service',
                                            'Grade'),
                            'get_grade',
                            searcher='search_grade')

    @classmethod
    def search_grade(cls, name, clause):
        return [('lines.service', ) + tuple(clause[1:])]

    @fields.depends('enrolment', 'currency', 'party', 'start_date')
    def on_change_enrolment(self):
        pool = Pool()
        Product = pool.get('product.product')

        if not self.enrolment:
            return

        party = None
        quantity = 1
        party_context = {}
        if self.party:
            party = self.party
            if party.lang:
                party_context['language'] = party.lang.code

        enrolment = self.enrolment
        category = enrolment.sale_uom.category
        if self.enrolment:
            unit = enrolment.sale_uom
            unit_digits = enrolment.sale_uom.digits

        with Transaction().set_context(self._get_context_sale_price()):
            self.unit_price_enrolment = Product.get_sale_price(
                [enrolment], quantity or 0)[enrolment.id]
            if self.unit_price_enrolment:
                self.unit_price_enrolment = self.unit_price_enrolment.quantize(
                    Decimal(1) /
                    10**self.__class__.unit_price_enrolment.digits[1])

    def _get_context_sale_price(self):
        context = {}
        '''if getattr(self, 'subscription', None):
            if getattr('currency', None):
                context['currency'] = self.currency.id
            if getattr('party', None):
                context['customer'] = self.party.id
            if getattr('start_date', None):
                context['sale_date'] = self.start_date'''
        if self.currency:
            context['currency'] = self.currency.id
        if self.party:
            context['customer'] = self.party.id
        if self.start_date:
            context['sale_date'] = self.start_date

        if self.enrolment:
            context['uom'] = self.enrolment.default_uom.id
        return context

    @classmethod
    def __setup__(cls):
        super(Subscription, cls).__setup__()
        cls.party.depends = ['company', 'state']
        cls.party.states = {'readonly': Eval('state') != 'draft'}
        cls.company.domain = []
        cls.party.domain = [
            'AND',
            [('is_subscriber', '=', True)],
            [('company', '=', Eval('company', -1))],
        ]
        cls.lines.required = True
        cls.end_date.required = True
        cls._buttons.update({
            'cancel': {
                'invisible': ~Eval('state').in_(['draft', 'quotation']),
                'icon': 'tryton-cancel',
            },
            'draft': {
                'invisible':
                Eval('state').in_(['draft', 'closed']),
                'icon':
                If(
                    Eval('state') == 'canceled', 'tryton-clear',
                    'tryton-go-previous'),
            },
            'quote': {
                'pre_validate': [
                    'OR',
                    ('registration_date', '!=', None),
                    ('enrolment', '!=', None),
                    ('lines', '!=', []),
                ],
                'invisible':
                Eval('state') != 'draft',
                'icon':
                'tryton-go-next',
            },
            'run': {
                #'invisible': Eval('state') == 'running',
                'invisible': Eval('state').in_(['running', 'canceled']),
                'icon': 'tryton-go-next',
            },
        })
        cls._transitions |= set((
            ('draft', 'canceled'),
            ('draft', 'quotation'),
            ('draft', 'running'),
            ('quotation', 'canceled'),
            ('quotation', 'draft'),
            ('quotation', 'running'),
            ('running', 'draft'),
            ('running', 'closed'),
            ('canceled', 'draft'),
        ))
        cls._error_messages.update({
            'missing_account_enrolment':
            ("You need to define an enrolment account."),
        })

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [
            bool_op,
            ('party', ) + tuple(clause[1:]),
            ('student', ) + tuple(clause[1:]),
        ]

    def get_receivable_payable(self, name):
        amount = self.student.receivable
        return amount

    def get_grade(self, name):
        return self.lines[0].service.id if self.lines else None

    def get_amount(self, name):
        amount = Decimal('0.0')
        if self.lines:
            for line in self.lines:
                if line.unit_price is not None:
                    amount += line.unit_price
        return amount

    def _create_party_address(self, party):
        pool = Pool()
        Party = pool.get('party.party')
        PartyAddress = pool.get('party.address')

        address = PartyAddress(party=party, city="Guatemala")
        address.save()
        #print "PARTY ID: " + str(address.id) +"ADDRESS:  " + str(address.city) + "PARTY: " + str(address.party)
        #print "PARTY ADDRESSES:  " + str(party.addresses)
        return address

    def _get_invoice(self, invoice_date=None):

        pool = Pool()
        Date = pool.get('ir.date')
        Invoice = pool.get('account.invoice')

        address = self.student.address_get(type='invoice')
        if address is None:
            self._create_party_address(self.student)
        invoice = Invoice(
            company=self.company,
            type='out',
            party=self.student,
            invoice_address=self.student.address_get(type='invoice'),
            currency=self.currency,
            invoice_date=invoice_date,
            accounting_date=invoice_date,
            reference='MAT: ' + self.number,
            description=mes_actual(invoice_date),
            account=self.party.account_receivable,
            subscription_origin=self,
        )
        invoice.on_change_type()
        invoice.payment_term = self.payment_term
        return invoice

    @classmethod
    def _check_enrolment(cls, party=None, reference=None):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        invoices = Invoice.search([
            ('party', '=', party),
            ('reference', '=', reference),
            ('is_enrolment', '=', True),
        ])
        if invoices:
            return True
        return False

    def compute_next_invoice_date(self):
        start_date = self.invoice_start_date or self.start_date
        date = self.next_invoice_date or self.start_date
        rruleset = self.invoice_recurrence.rruleset(start_date)
        dt = datetime.datetime.combine(date, datetime.time())
        inc = (start_date == date) and not self.next_invoice_date
        next_date = rruleset.after(dt, inc=inc)
        return next_date.date()

    def create_enrolment_invoice(self):
        'Create and return an enrolment invoice'
        pool = Pool()
        Invoice = pool.get('account.invoice')
        InvoiceLine = pool.get('account.invoice.line')
        Uom = pool.get('product.uom')
        AccountConfiguration = pool.get('account.configuration')
        account_config = AccountConfiguration(1)

        pool = Pool()
        Invoice = pool.get('account.invoice')

        address = self.student.address_get(type='invoice')
        if address is None:
            self._create_party_address(self.student)

        invoice = Invoice(
            company=self.company,
            type='out',
            party=self.student,
            invoice_address=self.student.address_get(type='invoice'),
            currency=self.currency,
            reference='MAT: ' + str(self.number),
            description='INS - ' + str(self.registration_date.year),
            invoice_date=self.registration_date,
            accounting_date=self.registration_date,
            is_enrolment=True,
            subscription_origin=self,
        )
        invoice.on_change_type()
        invoice.payment_term = self.payment_term
        default_enrolment_account = account_config.get_multivalue(
            'default_enrolment_account')
        default_enrolment_revenue = account_config.get_multivalue(
            'default_enrolment_revenue')
        if default_enrolment_account is None:
            self.raise_user_error('missing_account_enrolment')
        if default_enrolment_revenue is None:
            self.raise_user_error('missing_account_enrolment')

        invoice.account = default_enrolment_account
        invoice.save()
        invoice.update_taxes([invoice])

        line = InvoiceLine()
        line.invoice_type = 'out'
        line.type = 'line'
        line.quantity = 1
        line.unit = self.enrolment.default_uom
        line.unit_price = self.unit_price_enrolment
        line.product = self.enrolment
        line.description = self.enrolment.name
        line.party = self.student
        line.account = default_enrolment_revenue
        line.invoice = invoice

        if not line.account:
            cls.raise_user_error('missing_account_revenue', {
                'product': enrolment.rec_name,
            })

        taxes = []
        pattern = line._get_tax_rule_pattern()
        party = invoice.party
        for tax in line.product.customer_taxes_used:
            if party.customer_tax_rule:
                tax_ids = party.customer_tax_rule.apply(tax, pattern)
                if tax_ids:
                    taxes.extend(tax_ids)
                continue
            taxes.append(tax.id)
        if party.customer_tax_rule:
            tax_ids = party.customer_tax_rule.apply(None, pattern)
            if tax_ids:
                taxes.extend(tax_ids)
        line.taxes = taxes
        line.save()

        return invoice

    @classmethod
    def generate_invoice(cls, date=None, party=None, enrolment=None):
        pool = Pool()
        Date = pool.get('ir.date')
        Consumption = pool.get('sale.subscription.line.consumption')
        Invoice = pool.get('account.invoice')
        InvoiceLine = pool.get('account.invoice.line')
        Subscription = pool.get('sale.subscription')
        company = Transaction().context.get('company')

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

        consumptions = Consumption.search(
            [('invoice_line', '=', None),
             ('line.subscription.next_invoice_date', '<=', date),
             ('line.subscription.state', 'in', ['running', 'closed']),
             ('company', '=', company)],
            order=[
                ('line.subscription.id', 'DESC'),
            ])

        def keyfunc(consumption):
            return consumption.line.subscription

        invoices = {}
        lines = {}

        if consumptions:
            invoice_date = consumptions[0].date
        for subscription, consumptions in groupby(consumptions, key=keyfunc):
            invoices[subscription] = invoice = subscription._get_invoice(
                invoice_date)
            lines[subscription] = Consumption.get_invoice_lines(
                consumptions, invoice)

        all_invoices = invoices.values()

        Invoice.save(all_invoices)

        all_invoice_lines = []
        for subscription, invoice in invoices.items():
            invoice_lines, _ = lines[subscription]
            for line in invoice_lines:
                line.invoice = invoice
            all_invoice_lines.extend(invoice_lines)
        InvoiceLine.save(all_invoice_lines)

        all_consumptions = []
        for values in lines.values():
            for invoice_line, consumptions in zip(*values):
                for consumption in consumptions:
                    assert not consumption.invoice_line
                    consumption.invoice_line = invoice_line
                    all_consumptions.append(consumption)
        Consumption.save(all_consumptions)

        Invoice.update_taxes(all_invoices)

        subscriptions = cls.search([('next_invoice_date', '<=', date),
                                    ('company', '=', company)])

        for subscription in subscriptions:
            if subscription.state == 'running':
                while subscription.next_invoice_date <= date:
                    subscription.next_invoice_date = (
                        subscription.compute_next_invoice_date())
            else:
                subscription.next_invoice_date = None
        for subscription in subscriptions:
            # check invoice enrolment
            party = subscription.student.id
            subscription_reference = 'MAT: ' + str(subscription.number)
            exist_enrolment = subscription._check_enrolment(
                party, subscription_reference)

            if not exist_enrolment:
                subscription.create_enrolment_invoice()
        cls.save(subscriptions)

    @classmethod
    def generate_global_invoice(cls, date=None, party=None, enrolment=None):
        pool = Pool()
        Date = pool.get('ir.date')
        Consumption = pool.get('sale.subscription.line.consumption')
        Invoice = pool.get('account.invoice')
        InvoiceLine = pool.get('account.invoice.line')
        Subscription = pool.get('sale.subscription')

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

        consumptions = Consumption.search([
            ('invoice_line', '=', None),
            ('line.subscription.next_invoice_date', '<=', date),
            ('line.subscription.state', 'in', ['running', 'closed']),
        ],
                                          order=[
                                              ('line.subscription.id', 'DESC'),
                                          ])

        def keyfunc(consumption):
            return consumption.line.subscription

        invoices = {}
        lines = {}

        if consumptions:
            invoice_date = consumptions[0].date
        for subscription, consumptions in groupby(consumptions, key=keyfunc):
            invoices[subscription] = invoice = subscription._get_invoice(
                invoice_date)
            lines[subscription] = Consumption.get_invoice_lines(
                consumptions, invoice)

        all_invoices = invoices.values()

        Invoice.save(all_invoices)

        all_invoice_lines = []
        for subscription, invoice in invoices.items():
            invoice_lines, _ = lines[subscription]
            for line in invoice_lines:
                line.invoice = invoice
            all_invoice_lines.extend(invoice_lines)
        InvoiceLine.save(all_invoice_lines)

        all_consumptions = []
        for values in lines.values():
            for invoice_line, consumptions in zip(*values):
                for consumption in consumptions:
                    assert not consumption.invoice_line
                    consumption.invoice_line = invoice_line
                    all_consumptions.append(consumption)
        Consumption.save(all_consumptions)

        Invoice.update_taxes(all_invoices)

        subscriptions = cls.search([
            ('next_invoice_date', '<=', date),
        ])

        for subscription in subscriptions:
            if subscription.state == 'running':
                while subscription.next_invoice_date <= date:
                    subscription.next_invoice_date = (
                        subscription.compute_next_invoice_date())
            else:
                subscription.next_invoice_date = None
        cls.save(subscriptions)

    @classmethod
    @ModelView.button
    @Workflow.transition('running')
    @process_opportunity
    def run(cls, subscriptions):
        cls.set_number(subscriptions)
        pool = Pool()
        Line = pool.get('sale.subscription.line')
        Subscription = pool.get('sale.subscription')
        Party = pool.get('party.party')
        lines = []
        state = 'none'
        party = ''
        enrolment = ''
        for subscription in subscriptions:
            subscription.state = 'running'
            start_date = subscription.start_date
            state = subscription.state
            if not subscription.next_invoice_date:
                subscription.next_invoice_date = (
                    subscription.compute_next_invoice_date())
            for line in subscription.lines:
                if (line.next_consumption_date is None and not line.consumed):
                    line.next_consumption_date = (
                        line.compute_next_consumption_date())
            lines.extend(subscription.lines)
            party = subscription.party.id
            enrolment = subscription.enrolment
        Line.save(lines)
        cls.save(subscriptions)
        Line.generate_consumption(date=start_date)
        Subscription.generate_invoice(date=start_date,
                                      party=party,
                                      enrolment=enrolment)

    @classmethod
    def _get_origin(cls):
        'Return list of Model names for origin Reference'
        return ['sale.subscription', 'sale.opportunity']

    @classmethod
    def get_origin(cls):
        Model = Pool().get('ir.model')
        models = cls._get_origin()
        models = Model.search([
            ('model', 'in', models),
        ])
        return [(None, '')] + [(m.model, m.name) for m in models]

    @classmethod
    @process_opportunity
    def delete(cls, subscriptions):
        super(Subscription, cls).delete(subscriptions)

    @classmethod
    @process_opportunity
    def cancel(cls, subscriptions):
        super(Subscription, cls).cancel(subscriptions)

    @classmethod
    @process_opportunity
    def draft(cls, subscriptions):
        super(Subscription, cls).draft(subscriptions)

    @classmethod
    @process_opportunity
    def quote(cls, subscriptions):
        super(Subscription, cls).quote(subscriptions)

    @classmethod
    @process_opportunity
    def process(cls, subscriptions):
        super(Subscription, cls).process(subscriptions)

    @classmethod
    def default_invoice_recurrence(cls):
        Recurrence = Pool().get('sale.subscription.recurrence.rule.set')
        recurrences = Recurrence.search([])
        if recurrences:
            return recurrences[0].id
        return None

    @classmethod
    def default_registration_date(cls):
        Date = Pool().get('ir.date')
        date = Date.today()
        return date

    @classmethod
    def default_start_date(cls):
        Date = Pool().get('ir.date')
        today = Date.today()
        first_date = date(today.year, 1, 1)
        return first_date

    @classmethod
    def default_end_date(cls):
        Date = Pool().get('ir.date')
        today = Date.today()
        end_date = date(today.year, 10, 1)
        return end_date

    @classmethod
    def search(cls,
               domain,
               offset=0,
               limit=None,
               order=None,
               count=False,
               query=False):
        transaction = Transaction().context

        student = transaction.get('student')
        subscriber = transaction.get('subscriber')
        course = transaction.get('course')

        domain = domain[:]
        if student is not None:
            domain = [domain, ('student', '=', student)]
        if subscriber is not None:
            domain = [domain, ('party', '=', subscriber)]
        if course is not None:
            domain = [domain, ('lines.service', '=', course)]

        records = super(Subscription, cls).search(domain,
                                                  offset=offset,
                                                  limit=limit,
                                                  order=order,
                                                  count=count,
                                                  query=query)

        if Transaction().user:
            # Clear the cache as it was not cleaned for confidential
            cache = Transaction().get_cache()
            cache.pop(cls.__name__, None)
        return records
Exemple #26
0
class Module(ModelSQL, ModelView):
    "Module"
    __name__ = "ir.module"
    name = fields.Char("Name", readonly=True, required=True)
    version = fields.Function(fields.Char('Version'), 'get_version')
    dependencies = fields.One2Many('ir.module.dependency',
                                   'module',
                                   'Dependencies',
                                   readonly=True)
    parents = fields.Function(fields.One2Many('ir.module', None, 'Parents'),
                              'get_parents')
    childs = fields.Function(fields.One2Many('ir.module', None, 'Childs'),
                             'get_childs')
    state = fields.Selection([
        ('not activated', 'Not Activated'),
        ('activated', 'Activated'),
        ('to upgrade', 'To be upgraded'),
        ('to remove', 'To be removed'),
        ('to activate', 'To be activated'),
    ],
                             string='State',
                             readonly=True)

    @classmethod
    def __setup__(cls):
        super(Module, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints = [
            ('name_uniq', Unique(table, table.name),
             'The name of the module must be unique!'),
        ]
        cls._order.insert(0, ('name', 'ASC'))
        cls.__rpc__.update({
            'on_write': RPC(instantiate=0),
        })
        cls._buttons.update({
            'activate': {
                'invisible': Eval('state') != 'not activated',
                'depends': ['state'],
            },
            'activate_cancel': {
                'invisible': Eval('state') != 'to activate',
                'depends': ['state'],
            },
            'deactivate': {
                'invisible': Eval('state') != 'activated',
                'depends': ['state'],
            },
            'deactivate_cancel': {
                'invisible': Eval('state') != 'to remove',
                'depends': ['state'],
            },
            'upgrade': {
                'invisible': Eval('state') != 'activated',
                'depends': ['state'],
            },
            'upgrade_cancel': {
                'invisible': Eval('state') != 'to upgrade',
                'depends': ['state'],
            },
        })

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        sql_table = cls.__table__()
        model_data_sql_table = ModelData.__table__()
        cursor = Transaction().connection.cursor()

        # Migration from 3.6: remove double module
        old_table = 'ir_module_module'
        if backend.TableHandler.table_exist(old_table):
            backend.TableHandler.table_rename(old_table, cls._table)

        super(Module, cls).__register__(module_name)

        # Migration from 4.0: rename installed to activated
        cursor.execute(*sql_table.update([sql_table.state], ['activated'],
                                         where=sql_table.state == 'installed'))
        cursor.execute(
            *sql_table.update([sql_table.state], ['not activated'],
                              where=sql_table.state == 'uninstalled'))

        # Migration from 4.6: register buttons on ir module
        button_fs_ids = [
            'module_activate_button',
            'module_activate_cancel_button',
            'module_deactivate_button',
            'module_deactivate_cancel_button',
            'module_upgrade_button',
            'module_upgrade_cancel_button',
        ]
        cursor.execute(*model_data_sql_table.update(
            [model_data_sql_table.module], ['ir'],
            where=((model_data_sql_table.module == 'res')
                   & (model_data_sql_table.fs_id.in_(button_fs_ids)))))

    @staticmethod
    def default_state():
        return 'not activated'

    def get_version(self, name):
        return get_module_info(self.name).get('version', '')

    @classmethod
    def get_parents(cls, modules, name):
        parent_names = list(
            set(d.name for m in modules for d in m.dependencies))
        parents = cls.search([
            ('name', 'in', parent_names),
        ])
        name2id = dict((m.name, m.id) for m in parents)
        return dict(
            (m.id, [name2id[d.name] for d in m.dependencies]) for m in modules)

    @classmethod
    def get_childs(cls, modules, name):
        child_ids = dict((m.id, []) for m in modules)
        name2id = dict((m.name, m.id) for m in modules)
        childs = cls.search([
            ('dependencies.name', 'in', list(name2id.keys())),
        ])
        for child in childs:
            for dep in child.dependencies:
                if dep.name in name2id:
                    child_ids[name2id[dep.name]].append(child.id)
        return child_ids

    @classmethod
    def delete(cls, records):
        for module in records:
            if module.state in (
                    'activated',
                    'to upgrade',
                    'to remove',
                    'to activate',
            ):
                raise AccessError(gettext('ir.msg_module_delete_state'))
        return super(Module, cls).delete(records)

    @classmethod
    def on_write(cls, modules):
        dependencies = set()

        def get_parents(module):
            parents = set(p.id for p in module.parents)
            for p in module.parents:
                parents.update(get_parents(p))
            return parents

        def get_childs(module):
            childs = set(c.id for c in module.childs)
            for c in module.childs:
                childs.update(get_childs(c))
            return childs

        for module in modules:
            dependencies.update(get_parents(module))
            dependencies.update(get_childs(module))
        return list(dependencies)

    @classmethod
    @ModelView.button
    @filter_state('not activated')
    def activate(cls, modules):
        modules_activated = set(modules)

        def get_parents(module):
            parents = set(p for p in module.parents)
            for p in module.parents:
                parents.update(get_parents(p))
            return parents

        for module in modules:
            modules_activated.update(
                (m for m in get_parents(module) if m.state == 'not activated'))
        cls.write(list(modules_activated), {
            'state': 'to activate',
        })

    @classmethod
    @ModelView.button
    @filter_state('activated')
    def upgrade(cls, modules):
        modules_activated = set(modules)

        def get_childs(module):
            childs = set(c for c in module.childs)
            for c in module.childs:
                childs.update(get_childs(c))
            return childs

        for module in modules:
            modules_activated.update(
                (m for m in get_childs(module) if m.state == 'activated'))
        cls.write(list(modules_activated), {
            'state': 'to upgrade',
        })

    @classmethod
    @ModelView.button
    @filter_state('to activate')
    def activate_cancel(cls, modules):
        cls.write(modules, {
            'state': 'not activated',
        })

    @classmethod
    @ModelView.button
    @filter_state('activated')
    def deactivate(cls, modules):
        pool = Pool()
        Module = pool.get('ir.module')
        Dependency = pool.get('ir.module.dependency')
        module_table = Module.__table__()
        dep_table = Dependency.__table__()
        cursor = Transaction().connection.cursor()
        for module in modules:
            cursor.execute(*dep_table.join(
                module_table, condition=(dep_table.module == module_table.id)
            ).select(
                module_table.state,
                module_table.name,
                where=(dep_table.name == module.name)
                & NotIn(module_table.state, ['not activated', 'to remove'])))
            res = cursor.fetchall()
            if res:
                raise DeactivateDependencyError(
                    gettext('ir.msg_module_deactivate_dependency'),
                    '\n'.join('\t%s: %s' % (x[0], x[1]) for x in res))
        cls.write(modules, {'state': 'to remove'})

    @classmethod
    @ModelView.button
    @filter_state('to remove')
    def deactivate_cancel(cls, modules):
        cls.write(modules, {'state': 'not activated'})

    @classmethod
    @ModelView.button
    @filter_state('to upgrade')
    def upgrade_cancel(cls, modules):
        cls.write(modules, {'state': 'activated'})

    @classmethod
    def update_list(cls):
        'Update the list of available packages'
        count = 0
        module_names = get_module_list()

        modules = cls.search([])
        name2id = dict((m.name, m.id) for m in modules)
        cls.delete([
            m for m in modules
            if m.state != 'activated' and m.name not in module_names
        ])

        # iterate through activated modules and mark them as being so
        for name in module_names:
            if name in name2id:
                module = cls(name2id[name])
                tryton = get_module_info(name)
                cls._update_dependencies(module, tryton.get('depends', []))
                continue

            tryton = get_module_info(name)
            if not tryton:
                continue
            module, = cls.create([{
                'name': name,
                'state': 'not activated',
            }])
            count += 1
            cls._update_dependencies(module, tryton.get('depends', []))
        return count

    @classmethod
    def _update_dependencies(cls, module, depends=None):
        pool = Pool()
        Dependency = pool.get('ir.module.dependency')
        Dependency.delete(
            [x for x in module.dependencies if x.name not in depends])
        if depends is None:
            depends = []
        # Restart Browse Cache for deleted dependencies
        module = cls(module.id)
        dependency_names = [x.name for x in module.dependencies]
        to_create = []
        for depend in depends:
            if depend not in dependency_names:
                to_create.append({
                    'module': module.id,
                    'name': depend,
                })
        if to_create:
            Dependency.create(to_create)
Exemple #27
0
class BOMInput(ModelSQL, ModelView):
    "Bill of Material Input"
    __name__ = 'production.bom.input'
    _rec_name = 'product'

    bom = fields.Many2One('production.bom',
                          'BOM',
                          required=True,
                          select=1,
                          ondelete='CASCADE')
    product = fields.Many2One('product.product',
                              'Product',
                              required=True,
                              domain=[
                                  ('type', '!=', 'service'),
                              ])
    uom_category = fields.Function(
        fields.Many2One('product.uom.category', 'Uom Category'),
        'on_change_with_uom_category')
    uom = fields.Many2One('product.uom',
                          'Uom',
                          required=True,
                          domain=[
                              ('category', '=', Eval('uom_category')),
                          ],
                          depends=['uom_category'])
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
                                  'on_change_with_unit_digits')
    quantity = fields.Float('Quantity',
                            required=True,
                            domain=[('quantity', '>=', 0)],
                            digits=(16, Eval('unit_digits', 2)),
                            depends=['unit_digits'])

    @classmethod
    def __setup__(cls):
        super(BOMInput, cls).__setup__()
        t = cls.__table__()
        cls._sql_constraints = [
            ('product_bom_uniq', Unique(t, t.product,
                                        t.bom), 'product_bom_uniq'),
        ]
        cls._error_messages.update({
            'product_bom_uniq':
            'Product must be unique per BOM.',
            'recursive_bom':
            'You can not create recursive BOMs.',
        })

    @fields.depends('product', 'uom')
    def on_change_product(self):
        if self.product:
            uoms = self.product.default_uom.category.uoms
            if (not self.uom or self.uom not in uoms):
                self.uom = self.product.default_uom
                self.unit_digits = self.product.default_uom.digits
        else:
            self.uom = None
            self.unit_digits = 2

    @fields.depends('product')
    def on_change_with_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom.category.id

    @fields.depends('uom')
    def on_change_with_unit_digits(self, name=None):
        if self.uom:
            return self.uom.digits
        return 2

    @classmethod
    def validate(cls, boms):
        super(BOMInput, cls).validate(boms)
        for bom in boms:
            bom.check_bom_recursion()

    def check_bom_recursion(self):
        '''
        Check BOM recursion
        '''
        self.product.check_bom_recursion()

    def compute_quantity(self, factor):
        return self.uom.round(self.quantity * factor)
class Sale:
    "Sale"
    __name__ = 'sale.sale'

    endicia_mailclass = fields.Many2One(
        'endicia.mailclass',
        'MailClass',
        states={
            'readonly': ~Eval('state').in_(['draft', 'quotation']),
        },
        depends=['state'])
    endicia_mailpiece_shape = fields.Selection(
        MAILPIECE_SHAPES,
        'Endicia MailPiece Shape',
        states={
            'readonly': ~Eval('state').in_(['draft', 'quotation']),
        },
        depends=['state'])
    is_endicia_shipping = fields.Function(
        fields.Boolean('Is Endicia Shipping?', readonly=True),
        'get_is_endicia_shipping')

    @classmethod
    def view_attributes(cls):
        return super(Sale, cls).view_attributes() + [
            ('//page[@id="endicia"]', 'states', {
                'invisible': ~Bool(Eval('is_endicia_shipping'))
            })
        ]

    def _get_weight_uom(self):
        """
        Returns uom for endicia
        """
        UOM = Pool().get('product.uom')

        if self.is_endicia_shipping:

            # Endicia by default uses this uom
            return UOM.search([('symbol', '=', 'oz')])[0]

        return super(Sale, self)._get_weight_uom()

    @staticmethod
    def default_endicia_mailclass():
        Config = Pool().get('sale.configuration')
        config = Config(1)
        return config.endicia_mailclass and config.endicia_mailclass.id or None

    @classmethod
    def __setup__(cls):
        super(Sale, cls).__setup__()
        cls._error_messages.update({
            'mailclass_missing':
            'Select a mailclass to ship using Endicia [USPS].'
        })
        cls._buttons.update({
            'update_endicia_shipment_cost': {
                'invisible': Eval('state') != 'quotation'
            }
        })

    @fields.depends('is_endicia_shipping', 'carrier')
    def on_change_carrier(self):
        super(Sale, self).on_change_carrier()

        self.is_endicia_shipping = self.carrier and \
            self.carrier.carrier_cost_method == 'endicia' or None

    def _get_carrier_context(self):
        "Pass sale in the context"
        context = super(Sale, self)._get_carrier_context()

        if not self.carrier.carrier_cost_method == 'endicia':
            return context

        context = context.copy()
        context['sale'] = self.id
        return context

    def on_change_lines(self):
        """Pass a flag in context which indicates the get_sale_price method
        of endicia carrier not to calculate cost on each line change
        """
        with Transaction().set_context({'ignore_carrier_computation': True}):
            return super(Sale, self).on_change_lines()

    def apply_endicia_shipping(self):
        "Add a shipping line to sale for endicia"
        Currency = Pool().get('currency.currency')

        if self.carrier and self.carrier.carrier_cost_method == 'endicia':
            if not self.endicia_mailclass:
                self.raise_user_error('mailclass_missing')
            with Transaction().set_context(self._get_carrier_context()):
                shipment_cost_usd = self.carrier.get_sale_price()
                if not shipment_cost_usd[0]:
                    return
            # Convert the shipping cost to sale currency from USD
            usd, = Currency.search([('code', '=', 'USD')])
            shipment_cost = Currency.compute(usd, shipment_cost_usd[0],
                                             self.currency)
            self.add_shipping_line(
                shipment_cost, '%s - %s' %
                (self.carrier.party.name, self.endicia_mailclass.name))

    @classmethod
    def quote(cls, sales):
        res = super(Sale, cls).quote(sales)
        cls.update_endicia_shipment_cost(sales)
        return res

    @classmethod
    @ModelView.button
    def update_endicia_shipment_cost(cls, sales):
        "Updates the shipping line with new value if any"
        for sale in sales:
            sale.apply_endicia_shipping()

    def create_shipment(self, shipment_type):
        Shipment = Pool().get('stock.shipment.out')

        with Transaction().set_context(ignore_carrier_computation=True):
            # disable `carrier cost computation`(default behaviour) as cost
            # should only be computed after updating mailclass else error may
            # occur, with improper mailclass.
            shipments = super(Sale, self).create_shipment(shipment_type)
        if shipment_type == 'out' and shipments and self.carrier and \
                self.carrier.carrier_cost_method == 'endicia':
            Shipment.write(
                shipments, {
                    'endicia_mailclass': self.endicia_mailclass.id,
                    'endicia_mailpiece_shape': self.endicia_mailpiece_shape,
                    'is_endicia_shipping': self.is_endicia_shipping,
                })
        return shipments

    def get_endicia_shipping_cost(self, mailclass=None):
        """Returns the calculated shipping cost as sent by endicia

        :param mailclass: endicia mailclass for which cost to be fetched

        :returns: The shipping cost in USD
        """
        Carrier = Pool().get('carrier')
        EndiciaConfiguration = Pool().get('endicia.configuration')

        endicia_credentials = EndiciaConfiguration(1).get_endicia_credentials()
        carrier, = Carrier.search(['carrier_cost_method', '=', 'endicia'])

        if not mailclass and not self.endicia_mailclass:
            self.raise_user_error('mailclass_missing')

        from_address = self._get_ship_from_address()
        to_address = self.shipment_address
        to_zip = to_address.zip

        if to_address.country and to_address.country.code == 'US':
            # Domestic
            to_zip = to_zip and to_zip[:5]
        else:
            # International
            to_zip = to_zip and to_zip[:15]

        # Endicia only support 1 decimal place in weight
        weight_oz = "%.1f" % self.package_weight
        calculate_postage_request = CalculatingPostageAPI(
            mailclass=mailclass or self.endicia_mailclass.value,
            MailpieceShape=self.endicia_mailpiece_shape,
            weightoz=weight_oz,
            from_postal_code=from_address.zip and from_address.zip[:5],
            to_postal_code=to_zip,
            to_country_code=to_address.country and to_address.country.code,
            accountid=endicia_credentials.account_id,
            requesterid=endicia_credentials.requester_id,
            passphrase=endicia_credentials.passphrase,
            test=endicia_credentials.is_test,
        )

        # Logging.
        logger.debug('Making Postage Request for shipping cost of'
                     'Sale ID: {0} and Carrier ID: {1}'.format(
                         self.id, carrier.id))
        logger.debug('--------POSTAGE REQUEST--------')
        logger.debug(str(calculate_postage_request.to_xml()))
        logger.debug('--------END REQUEST--------')

        try:
            response = calculate_postage_request.send_request()
        except RequestError, e:
            self.raise_user_error(unicode(e))

        # Logging.
        logger.debug('--------POSTAGE RESPONSE--------')
        logger.debug(str(response))
        logger.debug('--------END RESPONSE--------')

        return self.fetch_endicia_postage_rate(
            objectify_response(response).PostagePrice)
Exemple #29
0
class SaleOpportunityLine(sequence_ordered(), ModelSQL, ModelView):
    'Sale Opportunity Line'
    __name__ = "sale.opportunity.line"
    _history = True
    _states = {
        'readonly':
        Eval('opportunity_state').in_(
            ['converted', 'won', 'lost', 'cancelled']),
    }

    opportunity = fields.Many2One('sale.opportunity',
                                  'Opportunity',
                                  ondelete='CASCADE',
                                  select=True,
                                  required=True,
                                  states={
                                      'readonly':
                                      _states['readonly']
                                      & Bool(Eval('opportunity')),
                                  })
    opportunity_state = fields.Function(
        fields.Selection('get_opportunity_states', "Opportunity State"),
        'on_change_with_opportunity_state')
    product = fields.Many2One('product.product',
                              'Product',
                              required=True,
                              domain=[('salable', '=', True)],
                              states=_states)
    quantity = fields.Float("Quantity",
                            digits='unit',
                            required=True,
                            states=_states)
    unit = fields.Many2One('product.uom',
                           'Unit',
                           required=True,
                           states=_states)

    del _states

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

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

    @fields.depends('opportunity', '_parent_opportunity.state')
    def on_change_with_opportunity_state(self, name=None):
        if self.opportunity:
            return self.opportunity.state

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

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

    def get_sale_line(self, sale):
        '''
        Return sale line for opportunity line
        '''
        SaleLine = Pool().get('sale.line')
        sale_line = SaleLine(
            type='line',
            product=self.product,
            sale=sale,
            description=None,
        )
        sale_line.on_change_product()
        self._set_sale_line_quantity(sale_line)
        sale_line.on_change_quantity()
        return sale_line

    def _set_sale_line_quantity(self, sale_line):
        sale_line.quantity = self.quantity
        sale_line.unit = self.unit

    def get_rec_name(self, name):
        pool = Pool()
        Lang = pool.get('ir.lang')
        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.opportunity.rec_name))

    @classmethod
    def search_rec_name(cls, name, clause):
        return [('product.rec_name', ) + tuple(clause[1:])]
Exemple #30
0
class AnalyticAccount(ModelSQL, ModelView):
    'Analytic Account'
    _name = 'ekd.account.analytic'
    _description = __doc__

    company = fields.Many2One('company.company', 'Company')
    model_ref = fields.Reference('Reference To',
                                 selection='get_model_ref',
                                 select=1)
    name = fields.Char('Name', required=True, select=1)
    code = fields.Char('Code', select=1)
    full_code = fields.Function(fields.Char('Full Code', select=1),
                                'get_full_code')
    full_name = fields.Function(fields.Char('Full name', select=1),
                                'get_full_name')
    active = fields.Boolean('Active', select=2)
    level = fields.Integer('Level Analysts', select=1)
    party = fields.Many2One('party.party', 'Party')
    currency = fields.Many2One('currency.currency', 'Currency', required=True)
    currency_digits = fields.Function(
        fields.Integer('Currency Digits', on_change_with=['currency']),
        'get_currency_digits')
    type = fields.Selection([
        ('root', 'Root'),
        ('view', 'View'),
        ('normal', 'Normal'),
        ('consolidation', 'Consolidation'),
    ],
                            'Type Struct',
                            required=True)

    kind_analytic = fields.Selection(
        [
            ('cost', 'Cost'),
            ('cash_flow', 'Cash Flow'),
            ('expense', 'Expense'),
            ('income', 'Income'),
            ('expense_future', 'Expense future period'),
            ('income_future', 'Incomes future period'),
            ('type_business', 'Type Business'),
            ('type_payment_budget', 'Types of payments to the budget'),
        ],
        'Kind analityc',
        states={
            'invisible': Not(In(Eval('type'), ['root', 'view'])),
            'required': In(Eval('type'), ['root', 'view']),
        })

    root = fields.Many2One('ekd.account.analytic',
                           'Root',
                           select=2,
                           domain=[('parent', '=', False)],
                           states={
                               'invisible': Equal(Eval('type'), 'root'),
                               'required': Not(Equal(Eval('type'), 'root')),
                           })
    parent = fields.Many2One('ekd.account.analytic',
                             'Parent',
                             select=2,
                             domain=[('parent', 'child_of', Eval('root'))],
                             states={
                                 'invisible': Equal(Eval('type'), 'root'),
                                 'required': Not(Equal(Eval('type'), 'root')),
                             })
    childs = fields.One2Many('ekd.account.analytic', 'parent', 'Children')
    child_consol_ids = fields.Many2Many('ekd.account.analytic.consoledate',
                                        'parent', 'child',
                                        'Consolidated Children')
    #    balance = fields.Function('get_balance', digits=(16, Eval('currency_digits', 2)),
    #            string='Balance')
    #    credit = fields.Function('get_credit_debit', digits=(16, Eval('currency_digits', 2)),
    #            string='Credit')
    #    debit = fields.Function('get_credit_debit', digits=(16, Eval('currency_digits', 2)),
    #            string='Debit')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('opened', 'Opened'),
        ('closed', 'Closed'),
    ],
                             'State',
                             required=True)
    note = fields.Text('Note')
    display_balance = fields.Selection([
        ('debit-credit', 'Debit - Credit'),
        ('credit-debit', 'Credit - Debit'),
    ],
                                       'Display Balance',
                                       required=True)
    mandatory = fields.Boolean('Mandatory',
                               states={
                                   'invisible':
                                   Not(Equal(Eval('type'), 'root')),
                               })

    def __init__(self):
        super(AnalyticAccount, self).__init__()
        self._constraints += [
            ('check_recursion', 'recursive_accounts'),
        ]
        self._error_messages.update({
            'recursive_accounts':
            'You can not create recursive accounts!',
        })
        self._order.insert(0, ('code', 'ASC'))
        self._order.insert(1, ('name', 'ASC'))

    def default_active(self):
        return True

    def default_company(self):
        return Transaction().context.get('company') or False

    def default_currency(self):
        company_obj = self.pool.get('company.company')
        currency_obj = self.pool.get('currency.currency')
        context = Transaction().context
        if context.get('company'):
            company = company_obj.browse(context['company'])
            return company.currency.id
        return False

    def default_type(self):
        return 'normal'

    def default_state(self):
        return 'draft'

    def default_display_balance(self):
        return 'credit-debit'

    def default_mandatory(self):
        return False

    def on_change_with_currency_digits(self, vals):
        currency_obj = self.pool.get('currency.currency')
        if vals.get('currency'):
            currency = currency_obj.browse(vals['currency'])
            return currency.digits
        return 2

    def get_currency_digits(self, ids, name):
        res = {}
        for account in self.browse(ids):
            res[account.id] = account.currency.digits
        return res

    def get_balance(self, ids, names):
        res = {}
        for name in names:
            res.setdefault(name, {})
            for account_id in ids:
                res[name][account_id] = Decimal('0.0')
        return res

    def get_credit_debit(self, ids, names):
        res = {}
        for name in names:
            res.setdefault(name, {})
            for account_id in ids:
                res[name][account_id] = Decimal('0.0')
        return res

    def get_rec_name(self, ids, name):
        if not ids:
            return {}
        res = {}
        for account in self.browse(ids):
            if account.code:
                res[account.id] = account.code + ' - ' + unicode(account.name)
            else:
                res[account.id] = unicode(account.name)
        return res

    def search_rec_name(self, name, clause):
        ids = self.search([('code', ) + clause[1:]], limit=1)
        if ids:
            return [('code', ) + clause[1:]]
        else:
            return [(self._rec_name, ) + clause[1:]]

    def get_full_name(self, ids, name):
        if not ids:
            return {}
        res = {}

        def _name(category):
            if category.id in res:
                return res[category.id]
            elif category.parent:
                return _name(category.parent) + '/' + category.name
            else:
                return category.name

        for category in self.browse(ids):
            res[category.id] = _name(category)
        return res

    def get_full_code(self, ids, name):
        if not ids:
            return {}
        res = {}

        def _name(account):
            if account.id in res:
                return res[account.id]
            elif account.parent:
                return _name(account.parent) + '.' + account.code
            else:
                return account.code

        for account in self.browse(ids):
            res[account.id] = _name(account)
        return res

    def create(self, vals):
        if vals.get('root'):
            vals['kind_analytic'] = self.browse(vals['root']).kind_analytic
        else:
            vals['kind_analytic'] = self.browse(vals['parent']).kind_analytic
        return super(AnalyticAccount, self).create(vals)

    def write(self, ids, vals):
        if vals.get('parent'):
            vals['kind_analytic'] = self.browse(vals['parent']).kind_analytic
        elif vals.get('root'):
            vals['kind_analytic'] = self.browse(vals['root']).kind_analytic
        return super(AnalyticAccount, self).write(ids, vals)

    def convert_view(self, tree):
        res = tree.xpath('//field[@name=\'analytic_accounts\']')
        if not res:
            return
        element_accounts = res[0]

        root_account_ids = self.search([
            ('parent', '=', False),
        ])
        if not root_account_ids:
            element_accounts.getparent().getparent().remove(
                element_accounts.getparent())
            return
        for account_id in root_account_ids:
            newelement = copy.copy(element_accounts)
            newelement.tag = 'label'
            newelement.set('name', 'analytic_account_' + str(account_id))
            element_accounts.addprevious(newelement)
            newelement = copy.copy(element_accounts)
            newelement.set('name', 'analytic_account_' + str(account_id))
            element_accounts.addprevious(newelement)
        parent = element_accounts.getparent()
        parent.remove(element_accounts)

    def analytic_accounts_fields_get(self, field, fields_names=None):
        res = {}
        if fields_names is None:
            fields_names = []

        root_account_ids = self.search([
            ('parent', '=', False),
        ])
        for account in self.browse(root_account_ids):
            name = 'analytic_account_' + str(account.id)
            if name in fields_names or not fields_names:
                res[name] = field.copy()
                #res[name]['required'] = account.mandatory
                res[name]['string'] = account.name
                res[name]['relation'] = self._name
                res[name]['domain'] = [('root', '=', account.id),
                                       ('type', '=', 'normal')]
        return res

    def get_model_ref(self):
        res = []
        res.append(['party.party', 'Partner'])
        res.append(['company.employee', 'Employee'])
        res.append(['product.product', 'Product'])
        res.append(['ekd.document', 'Document'])
        res.append(['project.project', 'Project'])
        return res