class Origin(origin_mixin(_states, _depends), ModelSQL, ModelView): "Account Statement Origin" __name__ = 'account.statement.origin' _rec_name = 'number' lines = fields.One2Many( 'account.statement.line', 'origin', "Lines", states={ 'readonly': ((Eval('statement_id', -1) < 0) | ~Eval('statement_state').in_(['draft', 'validated'])), }, domain=[ ('statement', '=', Eval('statement')), ('date', '=', Eval('date')), ], depends=['statement', 'date', 'statement_id']) statement_id = fields.Function(fields.Integer("Statement ID"), 'on_change_with_statement_id') pending_amount = fields.Function(fields.Numeric( "Pending Amount", digits=(16, Eval('_parent_statement', {}).get('currency_digits', 2))), 'on_change_with_pending_amount', searcher='search_pending_amount') informations = fields.Dict('account.statement.origin.information', "Informations", readonly=True) @fields.depends('statement') def on_change_with_statement_id(self, name=None): if self.statement: return self.statement.id return -1 @fields.depends('lines', 'amount') def on_change_with_pending_amount(self, name=None): lines_amount = sum( getattr(l, 'amount') or Decimal(0) for l in self.lines) return (self.amount or Decimal(0)) - lines_amount @classmethod def search_pending_amount(cls, name, clause): pool = Pool() Line = pool.get('account.statement.line') table = cls.__table__() line = Line.__table__() _, operator, value = clause Operator = fields.SQL_OPERATORS[operator] query = (table.join( line, 'LEFT', condition=line.origin == table.id).select( table.id, having=Operator(table.amount - Coalesce(Sum(line.amount), 0), value), group_by=table.id)) return [('id', 'in', query)]
class DictDefault(ModelSQL): 'Dict Default' __name__ = 'test.dict_default' dico = fields.Dict(None, 'Test Dict') @staticmethod def default_dico(): return dict(a=1)
class DictUnaccentedOff(ModelSQL): "Dict Unaccented Off" __name__ = 'test.dict_unaccented_off' dico = fields.Dict(None, "Dict") @classmethod def __setup__(cls): super().__setup__() cls.dico.search_unaccented = False
class TestAccess(ModelSQL): 'Test Access' __name__ = 'test.access' field1 = fields.Char('Field 1') field2 = fields.Char('Field 2') relate = fields.Many2One('test.access.relate', "Relate") reference = fields.Reference("Reference", [ (None, ""), ('test.access.relate', "Reference"), ]) dict_ = fields.Dict(None, "Dict")
class TakeSampleStart(ModelView): 'Take Sample Start' __name__ = 'lims.take.sample.start' date = fields.Date('Date', required=True) label = fields.Char('Label', required=True) attributes = fields.Dict('lims.sample.attribute', 'Attributes') sample = fields.Many2One('lims.sample', 'Sample') @staticmethod def default_date(): Date = Pool().get('ir.date') return Date.today()
class Product: __metaclass__ = PoolMeta __name__ = 'product.product' attributes = fields.Dict( 'product.attribute', 'Attributes', domain=[ ('sets', '=', Eval('_parent_template', {}).get('attribute_set', Eval('attribute_set', -1))), ], states={ 'readonly': (~Eval('attribute_set') & ~Eval('_parent_template', {}).get('attribute_set')), }, depends=['attribute_set'])
class Product(metaclass=PoolMeta): __name__ = 'product.product' attributes = fields.Dict( 'product.attribute', 'Attributes', domain=[ ('sets', '=', Eval('_parent_template', {}).get('attribute_set', Eval('attribute_set', -1))), ], states={ 'readonly': (~Eval('attribute_set') & ~Eval('_parent_template', {}).get('attribute_set')), }, depends=['attribute_set'], help="Add attributes to the variant.")
class ModelViewChangedValues(ModelView): 'ModelView Changed Values' __name__ = 'test.modelview.changed_values' name = fields.Char('Name') target = fields.Many2One('test.modelview.changed_values.target', 'Target') stored_target = fields.Many2One( 'test.modelview.changed_values.stored_target', "Stored Target") ref_target = fields.Reference('Target Reference', [ ('test.modelview.changed_values.target', 'Target'), ]) targets = fields.One2Many('test.modelview.changed_values.target', 'parent', 'Targets') m2m_targets = fields.Many2Many('test.modelview.changed_values.target', None, None, 'Targets') multiselection = fields.MultiSelection([ ('a', 'A'), ('b', 'B'), ], "MultiSelection") dictionary = fields.Dict('test.modelview.changed_values.dictionary', "Dictionary")
class Lot(metaclass=PoolMeta): __name__ = 'stock.lot' attributes = fields.Dict('stock.lot.attribute', 'Attributes', domain=[('type', 'in', Eval('attribute_types_domain'))], depends=['attribute_types_domain']) attributes_string = attributes.translated('attributes') attribute_types_domain = fields.Function( fields.Many2Many('stock.lot.attribute.type', None, None, 'Attribute Types domain'), 'on_change_with_attribute_types_domain') @fields.depends('product', '_parent_product.template') def on_change_with_attribute_types_domain(self, name=None): a_types = [] if self.product and self.product.template.categories: for cat in self.product.template.categories: for a_type in cat.lot_attribute_types: a_types.append(a_type.id) return a_types
class Product(metaclass=PoolMeta): __name__ = 'product.product' attributes = fields.Dict( 'product.attribute', 'Attributes', domain=[ ('sets', '=', Eval('_parent_template', {}).get('attribute_set', Eval('attribute_set', -1))), ], states={ 'readonly': (~Eval('attribute_set') & ~Eval('_parent_template', {}).get('attribute_set')), }, depends={'template'}, help="Add attributes to the variant.") attributes_name = fields.Function( fields.Char("Attributes Name", states={ 'invisible': ~Eval('attribute_set'), }), 'on_change_with_attributes_name') @fields.depends('attribute_set', 'attributes') def on_change_with_attributes_name(self, name=None): if not self.attribute_set or not self.attributes: return def key(attribute): return getattr(attribute, 'sequence', attribute.name) values = [] for attribute in sorted(self.attribute_set.attributes, key=key): if attribute.name in self.attributes: value = self.attributes[attribute.name] values.append( gettext('product_attribute.msg_label_value', label=attribute.string, value=attribute.format(value))) return " | ".join(filter(None, values))
class Product: __name__ = 'product.product' attributes = fields.Dict( 'product.attribute', 'Attributes', domain=[ ('sets', '=', Eval('_parent_template', {}).get('attribute_set', Eval('attribute_set', -1))), ], states={ 'readonly': (~Eval('attribute_set') & ~Eval('_parent_template', {}).get('attribute_set')), }, depends=['attribute_set']) attribute_set = fields.Function( fields.Many2One('product.attribute.set', 'Set'), 'on_change_with_attribute_set') @fields.depends('template') def on_change_with_attribute_set(self, name=None): if self.template and getattr(self.template, 'attribute_set', None): return self.template.attribute_set.id
class Queue(ModelSQL): "Queue" __name__ = 'ir.queue' name = fields.Char("Name", required=True) data = fields.Dict(None, "Data") enqueued_at = fields.Timestamp("Enqueued at", required=True) dequeued_at = fields.Timestamp("Dequeued at") finished_at = fields.Timestamp("Finished at") scheduled_at = fields.Timestamp("Scheduled at", help="When the task can start.") expected_at = fields.Timestamp("Expected at", help="When the task should be done.") @classmethod def __register__(cls, module_name): queue = cls.__table__() super().__register__(module_name) table_h = cls.__table_handler__(module_name) # Add index for candidates table_h.index_action([ queue.scheduled_at.nulls_first, queue.expected_at.nulls_first, queue.dequeued_at, queue.name, ], action='add') @classmethod def default_enqueued_at(cls): return datetime.datetime.now() @classmethod def copy(cls, records, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('enqueued_at') default.setdefault('dequeued_at') default.setdefault('finished_at') return super(Queue, cls).copy(records, default=default) @classmethod def push(cls, name, data, scheduled_at=None, expected_at=None): transaction = Transaction() database = transaction.database cursor = transaction.connection.cursor() with transaction.set_user(0): record, = cls.create([{ 'name': name, 'data': data, 'scheduled_at': scheduled_at, 'expected_at': expected_at, }]) if database.has_channel(): cursor.execute('NOTIFY "%s"', (cls.__name__,)) if not has_worker: transaction.tasks.append(record.id) return record.id @classmethod def pull(cls, database, connection, name=None): cursor = connection.cursor() queue = cls.__table__() candidates = With('id', 'scheduled_at', 'expected_at', query=queue.select( queue.id, queue.scheduled_at, queue.expected_at, where=((queue.name == name) if name else Literal(True)) & (queue.dequeued_at == Null), order_by=[ queue.scheduled_at.nulls_first, queue.expected_at.nulls_first])) selected = With('id', query=candidates.select( candidates.id, where=((candidates.scheduled_at <= CurrentTimestamp()) | (candidates.scheduled_at == Null)) & database.lock_id(candidates.id), order_by=[ candidates.scheduled_at.nulls_first, candidates.expected_at.nulls_first], limit=1)) next_timeout = With('seconds', query=candidates.select( Min(Extract('second', candidates.scheduled_at - CurrentTimestamp()) ), where=candidates.scheduled_at >= CurrentTimestamp())) task_id, seconds = None, None if database.has_returning(): query = queue.update([queue.dequeued_at], [CurrentTimestamp()], where=queue.id == selected.select(selected.id), with_=[candidates, selected, next_timeout], returning=[ queue.id, next_timeout.select(next_timeout.seconds)]) cursor.execute(*query) row = cursor.fetchone() if row: task_id, seconds = row else: query = queue.select(queue.id, where=queue.id == selected.select(selected.id), with_=[candidates, selected]) cursor.execute(*query) row = cursor.fetchone() if row: task_id, = row query = queue.update([queue.dequeued_at], [CurrentTimestamp()], where=queue.id == task_id) cursor.execute(*query) query = next_timeout.select(next_timeout.seconds) cursor.execute(*query) row = cursor.fetchone() if row: seconds, = row if not task_id and database.has_channel(): cursor.execute('LISTEN "%s"', (cls.__name__,)) return task_id, seconds def run(self): transaction = Transaction() Model = Pool().get(self.data['model']) with transaction.set_user(self.data['user']), \ transaction.set_context(self.data['context']): instances = self.data['instances'] # Ensure record ids still exist if isinstance(instances, int): with transaction.set_context(active_test=False): if Model.search([('id', '=', instances)]): instances = Model(instances) else: instances = None else: ids = set() with transaction.set_context(active_test=False): for sub_ids in grouped_slice(instances): records = Model.search([('id', 'in', list(sub_ids))]) ids.update(map(int, records)) if ids: instances = Model.browse( [i for i in instances if i in ids]) else: instances = None if instances is not None: getattr(Model, self.data['method'])( instances, *self.data['args'], **self.data['kwargs']) if not self.dequeued_at: self.dequeued_at = datetime.datetime.now() self.finished_at = datetime.datetime.now() self.save() @classmethod def caller(cls, model): return _Model(cls, model)
class DictUnaccentedOn(ModelSQL): "Dict Unaccented On" __name__ = 'test.dict_unaccented_on' dico = fields.Dict(None, "Dict")
class DictNoSchema(ModelSQL): "Dict No Schema" __name__ = 'test.dict_noschema' dico = fields.Dict(None, "Dict")
class DictText(ModelSQL): 'Dict TEXT' __name__ = 'test.dict_text' dico = fields.Dict('test.dict.schema', 'Test Dict') dico._sql_type = 'TEXT'
class ResultsReportVersionDetailSample(metaclass=PoolMeta): __name__ = 'lims.results_report.version.detail.sample' diagnosis = fields.Text('Diagnosis') diagnosis_plain = fields.Function(fields.Text('Diagnosis'), 'get_diagnosis_plain', setter='set_diagnosis_plain') diagnosis_states = fields.Dict('lims.diagnosis.state', 'States', domain=[('id', 'in', Eval('diagnosis_states_domain'))], depends=['diagnosis_states_domain']) diagnosis_states_string = diagnosis_states.translated('diagnosis_states') diagnosis_states_domain = fields.Function( fields.Many2Many('lims.diagnosis.state', None, None, 'States domain'), 'on_change_with_diagnosis_states_domain') diagnosis_warning = fields.Function(fields.Boolean('Diagnosis Warning'), 'get_notebook_field') template_type = fields.Function( fields.Selection([ (None, ''), ('base', 'HTML'), ('header', 'HTML - Header'), ('footer', 'HTML - Footer'), ], 'Report Template Type'), 'get_template_type') @classmethod def view_attributes(cls): return super().view_attributes() + [ ('//page[@id="diagnosis"]', 'states', { 'invisible': Not(Bool(Eval('template_type'))), }), ('//page[@id="diagnosis_plain"]', 'states', { 'invisible': Eval('template_type') == 'base', }), ] def get_diagnosis_plain(self, name): return self.diagnosis @classmethod def set_diagnosis_plain(cls, records, name, value): cls.write(records, {'diagnosis': value}) def get_template_type(self, name): return (self.version_detail.template and self.version_detail.template.type or None) @classmethod def create(cls, vlist): samples = super().create(vlist) for sample in samples: diagnosis_template = None report = sample.version_detail if report.diagnosis_template: diagnosis_template = report.diagnosis_template elif report.template and report.template.diagnosis_template: diagnosis_template = report.template.diagnosis_template if not diagnosis_template: continue save = False if not sample.diagnosis: content = diagnosis_template.content sample.diagnosis = content save = True if not sample.diagnosis_states: states = {} for state in diagnosis_template.diagnosis_states: states[state.name] = cls.get_default_diagnosis_state( sample, state.name) sample.diagnosis_states = states save = True if save: sample.save() return samples @fields.depends('version_detail', '_parent_version_detail.diagnosis_template') def on_change_with_diagnosis_states_domain(self, name=None): if (self.version_detail and self.version_detail.diagnosis_template and self.version_detail.diagnosis_template.diagnosis_states): return [ s.id for s in self.version_detail.diagnosis_template.diagnosis_states ] return [] @classmethod def _get_fields_from_sample(cls, sample, only_accepted=True): sample_default = super()._get_fields_from_sample(sample, only_accepted) sample_default['diagnosis'] = sample.diagnosis sample_default['diagnosis_states'] = sample.diagnosis_states return sample_default @classmethod def get_default_diagnosis_state(cls, sample, state_name): return '*'
class ResultsReportVersionDetailSample(metaclass=PoolMeta): __name__ = 'lims.results_report.version.detail.sample' plant = fields.Function(fields.Many2One('lims.plant', 'Plant'), 'get_notebook_field') equipment = fields.Function(fields.Many2One('lims.equipment', 'Equipment'), 'get_notebook_field') equipment_template = fields.Function( fields.Many2One('lims.equipment.template', 'Equipment Template'), 'get_notebook_field') equipment_model = fields.Function(fields.Char('Equipment Model'), 'get_notebook_field') equipment_serial_number = fields.Function( fields.Char('Equipment Serial Number'), 'get_notebook_field') equipment_name = fields.Function(fields.Char('Equipment Name'), 'get_notebook_field') component = fields.Function(fields.Many2One('lims.component', 'Component'), 'get_notebook_field') comercial_product = fields.Function( fields.Many2One('lims.comercial.product', 'Comercial Product'), 'get_notebook_field') precedent1 = fields.Many2One( 'lims.notebook', 'Precedent 1', domain=[ If(~Eval('free_precedents'), [('component', '=', Eval('component')), ('fraction.sample.state', '!=', 'annulled')], [('fraction.sample.state', '!=', 'annulled')]) ], depends=['free_precedents', 'component']) precedent2 = fields.Many2One( 'lims.notebook', 'Precedent 2', domain=[ If(~Eval('free_precedents'), [('component', '=', Eval('component')), ('fraction.sample.state', '!=', 'annulled')], [('fraction.sample.state', '!=', 'annulled')]) ], depends=['free_precedents', 'component']) precedent3 = fields.Many2One( 'lims.notebook', 'Precedent 3', domain=[ If(~Eval('free_precedents'), [('component', '=', Eval('component')), ('fraction.sample.state', '!=', 'annulled')], [('fraction.sample.state', '!=', 'annulled')]) ], depends=['free_precedents', 'component']) precedent4 = fields.Many2One( 'lims.notebook', 'Precedent 4', domain=[ If(~Eval('free_precedents'), [('component', '=', Eval('component')), ('fraction.sample.state', '!=', 'annulled')], [('fraction.sample.state', '!=', 'annulled')]) ], depends=['free_precedents', 'component']) precedent5 = fields.Many2One( 'lims.notebook', 'Precedent 5', domain=[ If(~Eval('free_precedents'), [('component', '=', Eval('component')), ('fraction.sample.state', '!=', 'annulled')], [('fraction.sample.state', '!=', 'annulled')]) ], depends=['free_precedents', 'component']) precedent6 = fields.Many2One( 'lims.notebook', 'Precedent 6', domain=[ If(~Eval('free_precedents'), [('component', '=', Eval('component')), ('fraction.sample.state', '!=', 'annulled')], [('fraction.sample.state', '!=', 'annulled')]) ], depends=['free_precedents', 'component']) precedent7 = fields.Many2One( 'lims.notebook', 'Precedent 7', domain=[ If(~Eval('free_precedents'), [('component', '=', Eval('component')), ('fraction.sample.state', '!=', 'annulled')], [('fraction.sample.state', '!=', 'annulled')]) ], depends=['free_precedents', 'component']) precedent8 = fields.Many2One( 'lims.notebook', 'Precedent 8', domain=[ If(~Eval('free_precedents'), [('component', '=', Eval('component')), ('fraction.sample.state', '!=', 'annulled')], [('fraction.sample.state', '!=', 'annulled')]) ], depends=['free_precedents', 'component']) free_precedents = fields.Boolean('Free precedents') precedent1_diagnosis = fields.Function( fields.Text('Diagnosis Precedent 1'), 'on_change_with_precedent1_diagnosis') precedent2_diagnosis = fields.Function( fields.Text('Diagnosis Precedent 2'), 'on_change_with_precedent2_diagnosis') precedent3_diagnosis = fields.Function( fields.Text('Diagnosis Precedent 3'), 'on_change_with_precedent3_diagnosis') precedent1_diagnosis_states = fields.Function( fields.Dict('lims.diagnosis.state', 'Diagnosis States Precedent 1'), 'get_precedent_diagnosis') precedent2_diagnosis_states = fields.Function( fields.Dict('lims.diagnosis.state', 'Diagnosis States Precedent 2'), 'get_precedent_diagnosis') precedent3_diagnosis_states = fields.Function( fields.Dict('lims.diagnosis.state', 'Diagnosis States Precedent 3'), 'get_precedent_diagnosis') @staticmethod def default_free_precedents(): return False @classmethod def view_attributes(cls): missing_diagnosis = True if 'diagnosis' not in cls._fields else False return super().view_attributes() + [ ('//group[@id="diagnosis"]', 'states', { 'invisible': missing_diagnosis, }), ] @fields.depends('precedent1') def on_change_with_precedent1_diagnosis(self, name=None): if self.precedent1: result = self.get_precedent_diagnosis((self, ), ('precedent1_diagnosis', )) return result['precedent1_diagnosis'][self.id] return None @fields.depends('precedent2') def on_change_with_precedent2_diagnosis(self, name=None): if self.precedent2: result = self.get_precedent_diagnosis((self, ), ('precedent2_diagnosis', )) return result['precedent2_diagnosis'][self.id] return None @fields.depends('precedent3') def on_change_with_precedent3_diagnosis(self, name=None): if self.precedent3: result = self.get_precedent_diagnosis((self, ), ('precedent3_diagnosis', )) return result['precedent3_diagnosis'][self.id] return None @classmethod def get_precedent_diagnosis(cls, samples, names): result = {} missing_diagnosis = True if 'diagnosis' not in cls._fields else False if missing_diagnosis: for name in names: result[name] = {} for s in samples: result[name][s.id] = None else: for name in names: result[name] = {} if 'precedent1' in name: for s in samples: result[name][s.id] = cls._get_precedent_diagnosis( s.precedent1, name) elif 'precedent2' in name: for s in samples: result[name][s.id] = cls._get_precedent_diagnosis( s.precedent2, name) else: # name == 'precedent3_diagnosis': for s in samples: result[name][s.id] = cls._get_precedent_diagnosis( s.precedent3, name) return result @classmethod def _get_precedent_diagnosis(cls, precedent, name): if not precedent: return None precedent_sample = cls.search([ ('notebook', '=', precedent), ]) if not precedent_sample: return None return (precedent_sample[0].diagnosis_states if 'states' in name else precedent_sample[0].diagnosis) @classmethod def _get_fields_from_sample(cls, sample, only_accepted=True): sample_default = super()._get_fields_from_sample(sample, only_accepted) sample_default['precedent1'] = (sample.precedent1 and sample.precedent1 or None) sample_default['precedent2'] = (sample.precedent2 and sample.precedent2 or None) sample_default['precedent3'] = (sample.precedent3 and sample.precedent3 or None) sample_default['precedent4'] = (sample.precedent4 and sample.precedent4 or None) sample_default['precedent5'] = (sample.precedent5 and sample.precedent5 or None) sample_default['precedent6'] = (sample.precedent6 and sample.precedent6 or None) sample_default['precedent7'] = (sample.precedent7 and sample.precedent7 or None) sample_default['precedent8'] = (sample.precedent8 and sample.precedent8 or None) return sample_default @classmethod def create(cls, vlist): samples = super().create(vlist) for sample in samples: if not sample.precedent1: precedents = cls.get_default_precedents(sample) if not precedents: continue for i in range(0, min(3, len(precedents))): setattr(sample, 'precedent%s' % str(i + 1), precedents[i]) sample.save() cls.update_precedent_lines(sample) return samples @classmethod def write(cls, *args): super().write(*args) update_precedent_lines = False if not update_precedent_lines: return actions = iter(args) for samples, vals in zip(actions, actions): change_precedents = False for field in [ 'precedent1', 'precedent2', 'precedent3', 'precedent4', 'precedent5', 'precedent6', 'precedent7', 'precedent8' ]: if field in vals: change_precedents = True if change_precedents: for sample in samples: cls.update_precedent_lines(sample) @staticmethod def get_default_precedents(sample): pool = Pool() Notebook = pool.get('lims.notebook') if not sample.component: return [] precedents = Notebook.search([ ('id', '!=', sample.notebook.id), ('component', '=', sample.component), ('fraction.sample.state', '!=', 'annulled'), ('invoice_party', '=', sample.notebook.invoice_party), ], order=[ ('fraction.sample.number', 'DESC'), ], limit=3) return precedents @classmethod def update_precedent_lines(cls, sample): pool = Pool() ResultsLine = pool.get('lims.results_report.version.detail.line') NotebookLine = pool.get('lims.notebook.line') precedent_lines = ResultsLine.search([ ('detail_sample', '=', sample.id), ('notebook_line', '=', None), ]) if precedent_lines: ResultsLine.delete(precedent_lines) result_lines = ResultsLine.search([ ('detail_sample', '=', sample.id), ]) analysis = [rl.notebook_line.analysis.id for rl in result_lines] lines_to_create = [] for precedent in [ sample.precedent1, sample.precedent2, sample.precedent3 ]: if not precedent: continue precedent_lines = NotebookLine.search([ ('notebook', '=', precedent), ('analysis', 'not in', analysis), ('accepted', '=', True), ]) for line in precedent_lines: lines_to_create.append({ 'detail_sample': sample.id, 'precedent_analysis': line.analysis.id, }) analysis.append(line.analysis.id) if lines_to_create: ResultsLine.create(lines_to_create)
class DictJSONB(ModelSQL): 'Dict JSONB' __name__ = 'test.dict_jsonb' dico = fields.Dict('test.dict.schema', 'Test Dict')
class StatementOrigin(metaclass=PoolMeta): __name__ = 'account.statement.origin' keywords = fields.Dict(None, "Keywords")
class Dict(ModelSQL): 'Dict' __name__ = 'test.dict' dico = fields.Dict('test.dict.schema', 'Test Dict') dico_string = dico.translated('dico') dico_string_keys = dico.translated('dico', 'keys')
class Origin(origin_mixin(_states), ModelSQL, ModelView): "Account Statement Origin" __name__ = 'account.statement.origin' _rec_name = 'number' lines = fields.One2Many( 'account.statement.line', 'origin', "Lines", states={ 'readonly': ((Eval('statement_id', -1) < 0) | ~Eval('statement_state').in_(['draft', 'validated'])), }, domain=[ ('statement', '=', Eval('statement', -1)), ('date', '=', Eval('date', None)), ]) statement_id = fields.Function( fields.Integer("Statement ID"), 'on_change_with_statement_id') pending_amount = fields.Function(Monetary( "Pending Amount", currency='currency', digits='currency'), 'on_change_with_pending_amount', searcher='search_pending_amount') information = fields.Dict( 'account.statement.origin.information', "Information", readonly=True) @classmethod def __register__(cls, module_name): table = cls.__table_handler__(module_name) # Migration from 5.0: rename informations into information table.column_rename('informations', 'information') super(Origin, cls).__register__(module_name) @fields.depends('statement', '_parent_statement.id') def on_change_with_statement_id(self, name=None): if self.statement: return self.statement.id return -1 @fields.depends('lines', 'amount') def on_change_with_pending_amount(self, name=None): lines_amount = sum( getattr(l, 'amount') or Decimal(0) for l in self.lines) return (self.amount or Decimal(0)) - lines_amount @classmethod def search_pending_amount(cls, name, clause): pool = Pool() Line = pool.get('account.statement.line') table = cls.__table__() line = Line.__table__() _, operator, value = clause Operator = fields.SQL_OPERATORS[operator] query = (table.join(line, 'LEFT', condition=line.origin == table.id) .select(table.id, having=Operator( table.amount - Coalesce(Sum(line.amount), 0), value), group_by=table.id)) return [('id', 'in', query)] @classmethod def copy(cls, origins, default=None): default = default.copy() if default is not None else {} default.setdefault('lines') return super().copy(origins, default=default)
class DictRequired(ModelSQL): 'Dict Required' __name__ = 'test.dict_required' dico = fields.Dict(None, 'Test Dict', required=True)
class LandedCost(Workflow, ModelSQL, ModelView, MatchMixin): 'Landed Cost' __name__ = 'account.landed_cost' _rec_name = 'number' number = fields.Char('Number', select=True, readonly=True) company = fields.Many2One('company.company', 'Company', required=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) shipments = fields.Many2Many('account.landed_cost-stock.shipment.in', 'landed_cost', 'shipment', 'Shipments', states={ 'readonly': Eval('state') != 'draft', }, domain=[ ('company', '=', Eval('company')), ('state', 'in', ['received', 'done']), ], depends=['company', 'state']) invoice_lines = fields.One2Many('account.invoice.line', 'landed_cost', 'Invoice Lines', states={ 'readonly': Eval('state') != 'draft', }, add_remove=[ ('landed_cost', '=', None), ], domain=[ ('company', '=', Eval('company', -1)), ('invoice.state', 'in', ['posted', 'paid']), ('invoice.type', '=', 'in'), ('product.landed_cost', '=', True), ('type', '=', 'line'), ], depends=['state', 'company']) allocation_method = fields.Selection([ ('value', 'By Value'), ], 'Allocation Method', required=True, states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) categories = fields.Many2Many( 'account.landed_cost-product.category', 'landed_cost', 'category', "Categories", states={ 'readonly': Eval('state') != 'draft', }, depends=['state'], help="Apply only to products of these categories.") products = fields.Many2Many('account.landed_cost-product.product', 'landed_cost', 'product', "Products", states={ 'readonly': Eval('state') != 'draft', }, depends=['state'], help="Apply only to these products.") posted_date = fields.Date('Posted Date', readonly=True) state = fields.Selection([ ('draft', 'Draft'), ('posted', 'Posted'), ('cancelled', 'Cancelled'), ], "State", readonly=True, sort=False) factors = fields.Dict(None, "Factors", readonly=True) @classmethod def __setup__(cls): super(LandedCost, cls).__setup__() cls._order = [ ('number', 'DESC'), ('id', 'DESC'), ] cls._transitions |= set(( ('draft', 'posted'), ('draft', 'cancelled'), ('posted', 'cancelled'), ('cancelled', 'draft'), )) cls._buttons.update({ 'cancel': { 'invisible': Eval('state') == 'cancelled', 'depends': ['state'], }, 'draft': { 'invisible': Eval('state') != 'cancelled', 'depends': ['state'], }, 'post': { 'invisible': Eval('state') != 'draft', 'depends': ['state'], }, }) @classmethod def __register__(cls, module_name): cursor = Transaction().connection.cursor() table_h = cls.__table_handler__(module_name) sql_table = cls.__table__() # Migration from 3.8: rename code into number if table_h.column_exist('code'): table_h.column_rename('code', 'number') super(LandedCost, cls).__register__(module_name) # Migration from 5.6: rename state cancel to cancelled cursor.execute(*sql_table.update([sql_table.state], ['cancelled'], where=sql_table.state == 'cancel')) @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_allocation_method(): return 'value' @staticmethod def default_state(): return 'draft' @classmethod @ModelView.button @Workflow.transition('cancelled') def cancel(cls, landed_costs): for landed_cost in landed_costs: if landed_cost.state == 'posted': getattr( landed_cost, 'unallocate_cost_by_%s' % landed_cost.allocation_method)() cls.write(landed_costs, { 'posted_date': None, 'factors': None, 'state': 'cancelled', }) @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, landed_costs): pass @property def cost(self): pool = Pool() Currency = pool.get('currency.currency') currency = self.company.currency cost = Decimal(0) for line in self.invoice_lines: with Transaction().set_context(date=line.invoice.currency_date): cost += Currency.compute(line.invoice.currency, line.amount, currency, round=False) return cost def stock_moves(self): moves = [] for shipment in self.shipments: for move in shipment.incoming_moves: if move.state == 'cancelled': continue if self._stock_move_filter(move): moves.append(move) return moves def _stock_move_filter(self, move): if not self.categories and not self.products: return True result = False if self.categories: result |= bool( set(self.categories) & set(_parents(move.product.categories_all))) if self.products: result |= bool(move.product in self.products) return result def _stock_move_filter_unused(self, moves): pool = Pool() Warning = pool.get('res.user.warning') categories = { c for m in moves for c in _parents(m.product.categories_all) } for category in self.categories: if category not in categories: key = '%s - %s' % (self, category) if Warning.check(key): raise FilterUnusedWarning( key, gettext( 'account_stock_landed_cost' '.msg_landed_cost_unused_category', landed_cost=self.rec_name, category=category.rec_name)) products = {m.product for m in moves} for product in self.products: if product not in products: key = '%s - %s' % (self, product) if Warning.check(key): raise FilterUnusedWarning( key, gettext( 'account_stock_landed_cost' '.msg_landed_cost_unused_product', landed_cost=self.rec_name, product=product.rec_name)) def allocate_cost_by_value(self): self.factors = self._get_value_factors() self._allocate_cost(self.factors) def unallocate_cost_by_value(self): factors = self.factors or self._get_value_factors() self._allocate_cost(factors, sign=-1) def _get_value_factors(self): "Return the factor for each move based on value" pool = Pool() Currency = pool.get('currency.currency') currency = self.company.currency moves = self.stock_moves() sum_value = 0 unit_prices = {} for move in moves: with Transaction().set_context(date=move.effective_date): unit_price = Currency.compute(move.currency, move.unit_price, currency, round=False) unit_prices[move.id] = unit_price sum_value += unit_price * Decimal(str(move.quantity)) factors = {} length = Decimal(len(moves)) for move in moves: quantity = Decimal(str(move.quantity)) if not sum_value: factors[str(move.id)] = 1 / length else: factors[str(move.id)] = (quantity * unit_prices[move.id] / sum_value) return factors def _allocate_cost(self, factors, sign=1): "Allocate cost on moves using factors" pool = Pool() Move = pool.get('stock.move') Currency = pool.get('currency.currency') assert sign in {1, -1} cost = self.cost currency = self.company.currency moves = [m for m in self.stock_moves() if m.quantity] costs = [] digit = Move.unit_price.digits[1] exp = Decimal(str(10.0**-digit)) difference = cost for move in moves: quantity = Decimal(str(move.quantity)) move_cost = cost * factors[str(move.id)] unit_landed_cost = round_price(move_cost / quantity, rounding=ROUND_DOWN) costs.append({ 'unit_landed_cost': unit_landed_cost, 'difference': move_cost - (unit_landed_cost * quantity), 'move': move, }) difference -= unit_landed_cost * quantity costs.sort(key=itemgetter('difference'), reverse=True) for cost in costs: move = cost['move'] quantity = Decimal(str(move.quantity)) if exp * quantity <= difference: cost['unit_landed_cost'] += exp difference -= exp * quantity if difference < exp: break for cost in costs: move = cost['move'] with Transaction().set_context(date=move.effective_date): unit_landed_cost = Currency.compute(currency, cost['unit_landed_cost'], move.currency, round=False) unit_landed_cost = round_price(unit_landed_cost, rounding=ROUND_HALF_EVEN) if move.unit_landed_cost is None: move.unit_landed_cost = 0 move.unit_price += unit_landed_cost * sign move.unit_landed_cost += unit_landed_cost * sign Move.save(moves) @classmethod @ModelView.button @Workflow.transition('posted') def post(cls, landed_costs): pool = Pool() Date = pool.get('ir.date') Warning = pool.get('res.user.warning') today = Date.today() for landed_cost in landed_costs: stock_moves = landed_cost.stock_moves() if not stock_moves: key = '%s post no move' % landed_cost if Warning.check(key): raise NoMoveWarning( key, gettext( 'account_stock_landed_cost' '.msg_landed_cost_post_no_stock_move', landed_cost=landed_cost.rec_name)) landed_cost._stock_move_filter_unused(stock_moves) getattr(landed_cost, 'allocate_cost_by_%s' % landed_cost.allocation_method)() for company, c_landed_costs in groupby(landed_costs, key=lambda l: l.company): with Transaction().set_context(company=company.id): today = Date.today() for landed_cost in c_landed_costs: landed_cost.posted_date = today landed_cost.posted_date = today # Use save as allocate methods may modify the records cls.save(landed_costs) @classmethod def create(cls, vlist): pool = Pool() Config = pool.get('account.configuration') vlist = [v.copy() for v in vlist] config = Config(1) default_company = cls.default_company() for values in vlist: if values.get('number') is None: values['number'] = config.get_multivalue( 'landed_cost_sequence', company=values.get('company', default_company)).get() return super(LandedCost, cls).create(vlist)