class LineConsumption(ModelSQL, ModelView): "Subscription Line Consumption" __name__ = 'sale.subscription.line.consumption' line = fields.Many2One( 'sale.subscription.line', "Line", required=True, select=True, ondelete='RESTRICT') quantity = fields.Float( "Quantity", digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']) unit_digits = fields.Function( fields.Integer("Unit Digits"), 'on_change_with_unit_digits') date = fields.Date("Date", required=True) invoice_line = fields.Many2One( 'account.invoice.line', "Invoice Line", readonly=True) @classmethod def __setup__(cls): super(LineConsumption, cls).__setup__() cls._order.insert(0, ('date', 'DESC')) cls._error_messages.update({ 'modify_invoiced_consumption': ( "You can not modify invoiced consumption."), 'delete_invoiced_consumption': ( "You can not delete invoiced consumption."), 'missing_account_revenue': ('Product "%(product)s" ' 'misses a revenue account.'), }) @fields.depends('line') def on_change_with_unit_digits(self, name=None): if self.line and self.line.unit: return self.line.unit.digits return 2 @classmethod def copy(cls, consumptions, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('invoice_line') return super(LineConsumption, cls).copy(consumptions, default=default) @classmethod def write(cls, *args): if any(c.invoice_line for consumptions in args[::2] for c in consumptions): cls.raise_user_error('modify_invoiced_consumption') super(LineConsumption, cls).write(*args) @classmethod def delete(cls, consumptions): if any(c.invoice_line for c in consumptions): cls.raise_user_error('delete_invoiced_consumption') super(LineConsumption, cls).delete(consumptions) @classmethod def get_invoice_lines(cls, consumptions, invoice): "Return a list of lines and a list of consumptions" pool = Pool() InvoiceLine = pool.get('account.invoice.line') lines, grouped_consumptions = [], [] consumptions = sorted(consumptions, key=cls._group_invoice_key) for key, sub_consumptions in groupby( consumptions, key=cls._group_invoice_key): sub_consumptions = list(sub_consumptions) line = InvoiceLine(**dict(key)) line.invoice_type = 'out' line.type = 'line' line.quantity = sum(c.quantity for c in sub_consumptions) line.account = line.product.account_revenue_used if not line.account: cls.raise_user_error('missing_account_revenue', { 'product': line.product.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 lines.append(line) grouped_consumptions.append(sub_consumptions) return lines, grouped_consumptions @classmethod def _group_invoice_key(cls, consumption): return ( ('company', consumption.line.subscription.company), ('unit', consumption.line.unit), ('product', consumption.line.service.product), ('unit_price', consumption.line.unit_price), ('description', consumption.line.description), ('origin', consumption.line), )
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, translate=True) code = fields.Char("Code", size=None, select=True, help="The internal identifier used for the location.") address = fields.Many2One('party.address', "Address", states={ 'invisible': Eval('type') != 'warehouse', }) type = fields.Selection([ ('supplier', 'Supplier'), ('customer', 'Customer'), ('lost_found', 'Lost and Found'), ('warehouse', 'Warehouse'), ('storage', 'Storage'), ('production', 'Production'), ('drop', 'Drop'), ('view', 'View'), ], "Location type") type_string = type.translated('type') parent = fields.Many2One("stock.location", "Parent", select=True, left="left", right="right", 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', 'required': Eval('type') == 'warehouse', }, domain=[ ('type', '=', 'storage'), [ 'OR', ('parent', 'child_of', [Eval('id', -1)]), ('parent', '=', None), ], ], help="Where incoming stock is received.") output_location = fields.Many2One( "stock.location", "Output", states={ 'invisible': Eval('type') != 'warehouse', 'required': Eval('type') == 'warehouse', }, domain=[('type', '=', 'storage'), [ 'OR', ('parent', 'child_of', [Eval('id', -1)]), ('parent', '=', None) ]], help="Where outgoing stock is sent from.") storage_location = fields.Many2One( "stock.location", "Storage", states={ 'invisible': Eval('type') != 'warehouse', 'required': Eval('type') == 'warehouse', }, domain=[('type', 'in', ['storage', 'view']), [ 'OR', ('parent', 'child_of', [Eval('id', -1)]), ('parent', '=', None) ]], help="The top level location where stock is stored.") picking_location = fields.Many2One( 'stock.location', 'Picking', states={ 'invisible': Eval('type') != 'warehouse', }, domain=[ ('type', '=', 'storage'), ('parent', 'child_of', [Eval('storage_location', -1)]), ], help="Where stock is picked from.\n" "Leave empty to use the storage location.") lost_found_location = fields.Many2One( 'stock.location', "Lost and Found", states={ 'invisible': Eval('type') != 'warehouse', 'readonly': ~Eval('active'), }, domain=[ ('type', '=', 'lost_found'), ], help="Used, by inventories, when correcting stock levels " "in the warehouse.") waste_locations = fields.Many2Many( 'stock.location.waste', 'warehouse', 'location', "Waste Locations", states={ 'invisible': Eval('type') != 'warehouse', }, domain=[ ('type', '=', 'lost_found'), ], help="The locations used for waste products from the warehouse.") waste_warehouses = fields.Many2Many( 'stock.location.waste', 'location', 'warehouse', "Waste Warehouses", states={ 'invisible': Eval('type') != 'lost_found', }, domain=[ ('type', '=', 'warehouse'), ], help="The warehouses that use the location for waste products.") quantity = fields.Function(fields.Float( "Quantity", digits=(16, Eval('quantity_uom_digits', 2)), help="The amount of stock in the location."), 'get_quantity', searcher='search_quantity') forecast_quantity = fields.Function(fields.Float( "Forecast Quantity", digits=(16, Eval('quantity_uom_digits', 2)), help="The amount of stock expected to be in the location."), 'get_quantity', searcher='search_quantity') quantity_uom = fields.Function( fields.Many2One('product.uom', "Quantity UOM"), 'get_quantity_uom') quantity_uom_digits = fields.Function( fields.Integer("Quantity UOM Digits"), 'get_quantity_uom') cost_value = fields.Function( fields.Numeric("Cost Value", digits=price_digits, 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 @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': ['view'], } @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_fields(cls, locations, field_names): super().validate_fields(locations, field_names) inactives = [] for location in locations: location.check_type_for_moves(field_names) if 'active' in field_names and not location.active: inactives.append(location) cls.check_inactive(inactives) def check_type_for_moves(self, field_names=None): """ Check locations with moves have types compatible with moves. """ pool = Pool() Move = pool.get('stock.move') if field_names and 'type' not in field_names: return invalid_move_types = ['warehouse', 'view'] 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']), ], order=[], limit=1) 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', 'cancelled']), [ '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 @property def lost_found_used(self): if self.warehouse: return self.warehouse.lost_found_location @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_grouping(cls): context = Transaction().context grouping, grouping_filter, key = (), (), [] if context.get('product') is not None: grouping = ('product', ) grouping_filter = ([context['product']], ) key = (context['product'], ) elif context.get('product_template') is not None: grouping = ('product.template', ) grouping_filter = ([context['product_template']], ) key = (context['product_template'], ) return grouping, grouping_filter, key @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)) 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 grouping, grouping_filter, key = cls._get_quantity_grouping() if not grouping: return {loc.id: None for loc in locations} 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_quantity_uom(cls, locations, name): pool = Pool() Product = pool.get('product.product') Template = pool.get('product.template') context = Transaction().context value = None uom = None if context.get('product') is not None: product = Product(context['product']) uom = product.default_uom elif context.get('product_template') is not None: template = Template(context['product_template']) uom = template.default_uom if uom: if name == 'quantity_uom': value = uom.id elif name == 'quantity_uom_digits': value = uom.digits return {l.id: value for l in locations} @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] = round_price( 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 @classmethod def view_attributes(cls): storage_types = Eval('type').in_(['storage', 'warehouse', 'view']) return super().view_attributes() + [ ('/tree/field[@name="quantity"]', 'visual', If(storage_types & (Eval('quantity', 0) < 0), 'danger', ''), ['type']), ('/tree/field[@name="forecast_quantity"]', 'visual', If(storage_types & (Eval('forecast_quantity', 0) < 0), 'warning', ''), ['type']), ]
class Party(DeactivableMixin, ModelSQL, ModelView, MultiValueMixin): "Party" __name__ = 'party.party' name = fields.Char( "Name", select=True, help="The main identifier of the party.") code = fields.Char('Code', required=True, select=True, states={ 'readonly': Eval('code_readonly', True), }, depends=['code_readonly'], help="The unique identifier of the party.") code_readonly = fields.Function(fields.Boolean('Code Readonly'), 'get_code_readonly') lang = fields.MultiValue( fields.Many2One('ir.lang', "Language", help="Used to translate communications with the party.")) langs = fields.One2Many( 'party.party.lang', 'party', "Languages") identifiers = fields.One2Many( 'party.identifier', 'party', "Identifiers", help="Add other identifiers of the party.") tax_identifier = fields.Function(fields.Many2One( 'party.identifier', 'Tax Identifier', help="The identifier used for tax report."), 'get_tax_identifier', searcher='search_tax_identifier') addresses = fields.One2Many('party.address', 'party', "Addresses") contact_mechanisms = fields.One2Many( 'party.contact_mechanism', 'party', "Contact Mechanisms") categories = fields.Many2Many( 'party.party-party.category', 'party', 'category', "Categories", help="The categories the party belongs to.") replaced_by = fields.Many2One('party.party', "Replaced By", readonly=True, states={ 'invisible': ~Eval('replaced_by'), }, help="The party replacing this one.") full_name = fields.Function(fields.Char('Full Name'), 'get_full_name') phone = fields.Function(fields.Char('Phone'), 'get_mechanism') mobile = fields.Function(fields.Char('Mobile'), 'get_mechanism') fax = fields.Function(fields.Char('Fax'), 'get_mechanism') email = fields.Function(fields.Char('E-Mail'), 'get_mechanism') website = fields.Function(fields.Char('Website'), 'get_mechanism') distance = fields.Function(fields.Integer('Distance'), 'get_distance') @classmethod def __setup__(cls): super(Party, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('code_uniq', Unique(t, t.code), 'party.msg_party_code_unique') ] cls._order.insert(0, ('distance', 'ASC NULLS LAST')) cls._order.insert(1, ('name', 'ASC')) cls.active.states.update({ 'readonly': Bool(Eval('replaced_by')), }) cls.active.depends.append('replaced_by') @classmethod def __register__(cls, module_name): super(Party, cls).__register__(module_name) table_h = cls.__table_handler__(module_name) # Migration from 3.8 table_h.not_null_action('name', 'remove') @staticmethod def order_code(tables): table, _ = tables[None] return [CharLength(table.code), table.code] @staticmethod def default_categories(): return Transaction().context.get('categories', []) @staticmethod def default_addresses(): if Transaction().user == 0: return [] return [{}] @classmethod def default_lang(cls, **pattern): Configuration = Pool().get('party.configuration') config = Configuration(1) lang = config.get_multivalue('party_lang', **pattern) return lang.id if lang else None @classmethod def default_code_readonly(cls, **pattern): Configuration = Pool().get('party.configuration') config = Configuration(1) return bool(config.get_multivalue('party_sequence', **pattern)) def get_code_readonly(self, name): return True @classmethod def tax_identifier_types(cls): return [ 'ad_nrt', 'al_nipt', 'ar_cuit', 'be_vat', 'bg_vat', 'by_unp', 'ch_vat', 'cl_rut', 'cn_uscc', 'co_rut', 'cr_cpj', 'cz_dic', 'de_vat', 'dk_cvr', 'do_rnc', 'ec_ruc', 'ee_kmkr', 'es_nif', 'eu_vat', 'fi_alv', 'fr_tva', 'gb_vat', 'gr_vat', 'gt_nit', 'hu_anum', 'id_npwp', 'ie_vat', 'il_hp', 'is_vsk', 'it_iva', 'jp_cn', 'kr_brn', 'lt_pvm', 'lu_tva', 'lv_pvn', 'mc_tva', 'md_idno', 'mt_vat', 'mx_rfc', 'nl_btw', 'no_mva', 'nz_ird', 'pe_ruc', 'pl_nip', 'pt_nif', 'py_ruc', 'ro_cf', 'rs_pib', 'ru_inn', 'se_vat', 'si_ddv', 'sk_dph', 'sm_coe', 'us_atin', 'us_ein', 'us_itin', 'us_ptin', 'us_ssn', 'us_tin', 'uy_ruc', 've_rif', 'za_tin'] def get_tax_identifier(self, name): types = self.tax_identifier_types() for identifier in self.identifiers: if identifier.type in types: return identifier.id @classmethod def search_tax_identifier(cls, name, clause): _, operator, value = clause types = cls.tax_identifier_types() domain = [ ('identifiers', 'where', [ ('code', operator, value), ('type', 'in', types), ]), ] # Add party without tax identifier if ((operator == '=' and value is None) or (operator == 'in' and None in value)): domain = ['OR', domain, [ ('identifiers', 'not where', [ ('type', 'in', types), ]), ], ] return domain def get_full_name(self, name): return self.name def get_mechanism(self, name): for mechanism in self.contact_mechanisms: if mechanism.type == name: return mechanism.value return '' @classmethod def _distance_query(cls, usages=None, party=None, depth=None): context = Transaction().context if party is None: party = context.get('related_party') if not party: return table = cls.__table__() return table.select( table.id.as_('to'), Literal(0).as_('distance'), where=(table.id == party)) @classmethod def get_distance(cls, parties, name): distances = {p.id: None for p in parties} query = cls._distance_query() if query: cursor = Transaction().connection.cursor() cursor.execute(*query.select( query.to.as_('to'), Min(query.distance).as_('distance'), group_by=[query.to])) distances.update(cursor) return distances @classmethod def order_distance(cls, tables): party, _ = tables[None] key = 'distance' if key not in tables: query = cls._distance_query() if not query: return [] query = query.select( query.to.as_('to'), Min(query.distance).as_('distance'), group_by=[query.to]) join = party.join(query, type_='LEFT', condition=query.to == party.id) tables[key] = { None: (join.right, join.condition), } else: query, _ = tables[key][None] return [query.distance] @classmethod def _new_code(cls, **pattern): pool = Pool() Configuration = pool.get('party.configuration') config = Configuration(1) sequence = config.get_multivalue('party_sequence', **pattern) if sequence: return sequence.get() @classmethod def create(cls, vlist): vlist = [x.copy() for x in vlist] for values in vlist: if not values.get('code'): values['code'] = cls._new_code() values.setdefault('addresses', None) return super(Party, cls).create(vlist) @classmethod def copy(cls, parties, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('code', None) return super(Party, cls).copy(parties, default=default) @classmethod def search_global(cls, text): for record, rec_name, icon in super(Party, cls).search_global(text): icon = icon or 'tryton-party' yield record, rec_name, icon def get_rec_name(self, name): if not self.name: return '[' + self.code + ']' return self.name @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' code_value = clause[2] if clause[1].endswith('like'): code_value = lstrip_wildcard(clause[2]) return [bool_op, ('code', clause[1], code_value) + tuple(clause[3:]), ('identifiers.code', clause[1], code_value) + tuple(clause[3:]), ('name',) + tuple(clause[1:]), ('contact_mechanisms.rec_name',) + tuple(clause[1:]), ] def address_get(self, type=None): """ Try to find an address for the given type, if no type matches the first address is returned. """ default_address = None if self.addresses: default_address = self.addresses[0] if type: for address in self.addresses: if getattr(address, type): return address return default_address def contact_mechanism_get(self, types=None, usage=None): """ Try to find a contact mechanism for the given types and usage, if no usage matches the first mechanism of the given types is returned. """ default_mechanism = None if types: if isinstance(types, str): types = {types} mechanisms = [m for m in self.contact_mechanisms if m.type in types] else: mechanisms = self.contact_mechanisms if mechanisms: default_mechanism = mechanisms[0] if usage: for mechanism in mechanisms: if getattr(mechanism, usage): return mechanism return default_mechanism
class ShipmentOut: "Shipment Out" __name__ = 'stock.shipment.out' is_international_shipping = fields.Function( fields.Boolean("Is International Shipping"), 'on_change_with_is_international_shipping') weight = fields.Function( fields.Float( "Weight", digits=(16, Eval('weight_digits', 2)), depends=['weight_digits'], ), 'get_weight') weight_uom = fields.Function(fields.Many2One('product.uom', 'Weight UOM'), 'get_weight_uom') weight_digits = fields.Function(fields.Integer('Weight Digits'), 'on_change_with_weight_digits') tracking_number = fields.Char('Tracking Number', states=STATES, depends=['state']) def get_weight(self, name=None): """ Returns sum of weight associated with each move line """ return sum( map(lambda move: move.get_weight(self.weight_uom, silent=True), self.outgoing_moves)) @fields.depends('weight_uom') def on_change_with_weight_digits(self, name=None): if self.weight_uom: return self.weight_uom.digits return 2 @classmethod def __setup__(cls): super(ShipmentOut, cls).__setup__() cls._buttons.update({ 'label_wizard': { 'invisible': Or((~Eval('state').in_(['packed', 'done'])), (Eval('tracking_number') != '')), 'icon': 'tryton-executable', }, }) cls._error_messages.update({ 'no_shipments': 'There must be atleast one shipment.', 'too_many_shipments': 'The wizard can be called on only one shipment', 'tracking_number_already_present': 'Tracking Number is already present for this shipment.', 'invalid_state': 'Labels can only be generated when the ' 'shipment is in Packed or Done states only', 'wrong_carrier': 'Carrier for selected shipment is not of %s', 'no_packages': 'Shipment %s has no packages', 'warehouse_address_missing': 'Warehouse address is missing', }) @classmethod def copy(cls, shipments, default=None): if default is None: default = {} default = default.copy() default['tracking_number'] = None return super(ShipmentOut, cls).copy(shipments, default=default) @classmethod @ModelView.button_action('shipping.wizard_generate_shipping_label') def label_wizard(cls, shipments): if len(shipments) == 0: cls.raise_user_error('no_shipments') elif len(shipments) > 1: cls.raise_user_error('too_many_shipments') @fields.depends('delivery_address', 'warehouse') def on_change_with_is_international_shipping(self, name=None): """ Return True if international shipping """ from_address = self._get_ship_from_address() if self.delivery_address and from_address and \ from_address.country and self.delivery_address.country and \ from_address.country != self.delivery_address.country: return True return False def get_weight_uom(self, name): """ Returns weight uom for the package """ return self._get_weight_uom().id def _get_weight_uom(self): """ Returns Pound as default value for uom Downstream module can override this method to change weight uom as per carrier """ UOM = Pool().get('product.uom') return UOM.search([('symbol', '=', 'lb')])[0] def _get_ship_from_address(self): """ Usually the warehouse from which you ship """ if self.warehouse and not self.warehouse.address: return self.raise_user_error('warehouse_address_missing') return self.warehouse and self.warehouse.address def allow_label_generation(self): """ Shipment must be in the right states and tracking number must not be present. """ if self.state not in ('packed', 'done'): self.raise_user_error('invalid_state') if self.tracking_number: self.raise_user_error('tracking_number_already_present') return True
class OverTimeAllowance(Workflow, ModelSQL, ModelView): """OverTime Allowance for an Employee""" __name__ = 'hr.allowance.ota' _STATES = {'readonly': ~Eval('state').in_(['draft'])} _DEPENDS = ['state'] salary_code = fields.Char('Salary Code', states=_STATES, required=True, depends=_DEPENDS) employee = fields.Many2One('company.employee', 'Employee Name', states=_STATES, required=True, depends=_DEPENDS) designation = fields.Many2One('employee.designation', 'Designation', states=_STATES, required=True, depends=_DEPENDS) department = fields.Many2One('company.department', 'Department', states=_STATES, required=True, depends=_DEPENDS) from_date = fields.Date('From Date', states=_STATES, depends=_DEPENDS) to_date = fields.Date('To Date', states=_STATES, depends=_DEPENDS) from_date = fields.Date('From Date', states=_STATES, depends=_DEPENDS) to_date = fields.Date('To Date', states=_STATES, depends=_DEPENDS) amount = fields.Integer('Amount', states=_STATES, required=True, depends=_DEPENDS) state = fields.Selection([ ('draft', 'Draft'), ('submit', 'Submit'), ('cash_section_officer', 'Cash Section_officer'), ('cancel', 'Cancel'), ('approve', 'Approve'), ], 'Status', readonly=True) @fields.depends('employee') def on_change_employee(self): if self.employee: self.salary_code = self.employee.salary_code if self.employee.salary_code else None self.designation = self.employee.designation if self.employee.designation else None self.department = self.employee.department if self.employee.department else None @staticmethod def default_state(): return 'draft' @staticmethod def default_from_date(): return datetime.date.today() @staticmethod def default_employee(): global current_employee current_employee = None pool = Pool() Employee = pool.get('company.employee') employee_id = Transaction().context.get('employee') employee = Employee.search([('id', '=', employee_id)]) if employee != []: current_employee = employee[0] return current_employee.id if current_employee else None @classmethod def __setup__(cls): super().__setup__() cls._buttons.update({ 'submit': { 'invisible': ~Eval('state').in_(['draft']) }, 'send_for_cash_section_officer_approval': { 'invisible': ~Eval('state').in_(['submit']) }, 'approve': { 'invisible': ~Eval('state').in_(['cash_section_officer']) }, 'cancel': { 'invisible': Eval('state').in_(['draft', 'approve', 'cancel']) }, }) cls._transitions |= set(( ('draft', 'submit'), ('submit', 'cash_section_officer'), ('submit', 'cancel'), ('cash_section_officer', 'approve'), ('cash_section_officer', 'cancel'), )) @classmethod @Workflow.transition('submit') def submit(cls, records): pass @classmethod @Workflow.transition('cash_section_officer') def send_for_cash_section_officer_approval(cls, records): pass @classmethod @Workflow.transition('approve') def approve(cls, records): pass @classmethod @Workflow.transition('cancel') def cancel(cls, records): pass
class RenumerationBill(Workflow, ModelSQL, ModelView): '''Examination Renumeration Bill''' __name__ = 'exam_section.renumeration_bill' state = fields.Selection([('draft', 'Draft'), ('confirm', 'Confirm'), ('aao_approval', 'AAO Approval'), ('ao_approval', 'AO Approval'), ('ace_approval', 'ACE Approval'), ('adean_approval', 'Assistant Dean Approval'), ('dean_approval', 'Dean Approval'), ('ao_approval_2', 'AO Approval 2'), ('approved', 'Approved'), ('rejected', 'Rejected')], 'Status', readonly=True) type_of_examiner = fields.Selection( [('internal', 'Internal'), ('external', 'External')], 'Type of Examiner', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) employee = fields.Many2One( 'company.employee', 'Employee', states={ 'invisible': Eval('type_of_examiner') == 'external', 'readonly': ~Eval('state').in_(['draft']) }, depends=['type_of_examiner', 'state'], ) examiner_name = fields.Char('Name of Examiner', states={ 'invisible': Eval('type_of_examiner') == 'internal', 'readonly': ~Eval('state').in_(['draft']) }, depends=['type_of_examiner', 'state']) designation = fields.Char( 'Designation', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) address = fields.Text('Address', states={ 'invisible': Eval('type_of_examiner') == 'internal', 'readonly': ~Eval('state').in_(['draft']) }, depends=['type_of_examiner', 'state']) pincode = fields.Integer('Pin Code', states={ 'invisible': Eval('type_of_examiner') == 'internal', 'readonly': ~Eval('state').in_(['draft']) }, depends=['type_of_examiner', 'state']) exam = fields.Many2One('exam_section.exam', 'Exam', states={ 'readonly': ~Eval('state').in_(['draft']), }, depends=['state']) course_name = fields.Char('Name of Course', states={ 'readonly': ~Eval('state').in_(['draft']), }, depends=['state']) purpose = fields.One2Many('exam_type.purpose_and_pay_renum', 'renumeration', 'Purpose', states={ 'readonly': ~Eval('state').in_(['draft']), }, depends=['state']) total_amount = fields.Function(fields.Float('Total Amount'), 'get_total_amount') net_amount = fields.Function( fields.Float( 'Net Amount', help='Less student benevolent fund(5%) deducted for AIIMS employee' ), 'get_net_amount') bank_name = fields.Char('Bank Name', states={ 'invisible': Eval('type_of_examiner') == 'internal', 'readonly': ~Eval('state').in_(['draft']) }, depends=['type_of_examiner', 'state']) bank_address = fields.Char('Bank Address', states={ 'invisible': Eval('type_of_examiner') == 'internal', 'readonly': ~Eval('state').in_(['draft']) }, depends=['type_of_examiner', 'state']) account_number = fields.Char('Account Number', states={ 'invisible': Eval('type_of_examiner') == 'internal', 'readonly': ~Eval('state').in_(['draft']) }, depends=['type_of_examiner', 'state']) ifsc_code = fields.Char('IFSC Code', states={ 'invisible': Eval('type_of_examiner') == 'internal', 'readonly': ~Eval('state').in_(['draft']) }, depends=['type_of_examiner', 'state']) @staticmethod def default_state(): return 'draft' @fields.depends('employee') def on_change_with_designation(self): '''Fill up employee designation field as soon as employee field is changed''' if self.employee: if self.employee.designation: return self.employee.designation.name def get_total_amount(self, name): '''Calculate total amount of Renumeration Bill''' res = 0 if self.purpose: for purpose in self.purpose: res += purpose.amount return res def get_net_amount(self, name): '''Calculate net amount of Renumeration Bill (5% is deducted for AIIMS Employee which goes to less student benevolent fund)''' res = 0 total_amount = self.total_amount if self.type_of_examiner == 'internal': res = 0.95 * (total_amount) else: res = total_amount return res @classmethod def __setup__(cls): '''Setup error messages, workflow transitions, and button properties when an instance of this class is initialized''' super().__setup__() cls._error_messages.update( {'submitted_bill': 'A Submitted Bill can not be deleted'}) cls._transitions = set( (('draft', 'confirm'), ('confirm', 'aao_approval'), ('aao_approval', 'ao_approval'), ('ao_approval', 'ace_approval'), ('ace_approval', 'adean_approval'), ('adean_approval', 'dean_approval'), ('dean_approval', 'ao_approval_2'), ('ao_approval_2', 'approved'))) cls._buttons.update({ 'confirm_data': { 'invisible': ~Eval('state').in_(['draft']) }, 'send_for_aao_approval': { 'invisible': ~Eval('state').in_(['confirm']) }, 'send_for_ao_approval': { 'invisible': ~Eval('state').in_(['aao_approval']) }, 'send_for_ace_approval': { 'invisible': ~Eval('state').in_(['ao_approval']) }, 'send_for_adean_approval': { 'invisible': ~Eval('state').in_(['ace_approval']) }, 'send_for_dean_approval': { 'invisible': ~Eval('state').in_(['adean_approval']) }, 'send_for_ao_approval_2': { 'invisible': ~Eval('state').in_(['dean_approval']) }, }) '''Button functions for executing workflow transitions (except delete)''' @classmethod @Workflow.transition('confirm') def confirm_data(cls, records): '''Change status of bill to confirm''' pass @classmethod @Workflow.transition('aao_approval') def send_for_aao_approval(cls, records): '''Change status of bill to aao_approval''' pass @classmethod @Workflow.transition('ao_approval') def send_for_ao_approval(cls, records): '''Change status of bill to ao_approval''' pass @classmethod @Workflow.transition('ace_approval') def send_for_ace_approval(cls, records): '''Change status of bill to ace_approval''' pass @classmethod @Workflow.transition('adean_approval') def send_for_adean_approval(cls, records): '''Change status of bill to adean_approval''' pass @classmethod @Workflow.transition('dean_approval') def send_for_dean_approval(cls, records): '''Change status of bill to dean_approval''' pass @classmethod @Workflow.transition('ao_approval_2') def send_for_ao_approval_2(cls, records): '''Change status of bill to ao_approval_2''' pass @classmethod def delete(cls, records): '''Override delete to ensure that the submitted bills are not deleted. ''' res = super().delete(records) for record in records: if record.state not in ('draft'): cls.raise_user_error('submitted_bill') return res
class TADABill(Workflow, ModelSQL, ModelView): '''Examination TA/DA Bill''' __name__ = 'exam_section.ta_da_bill' employee = fields.Many2One( 'company.employee', 'Employee', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) state = fields.Selection([('draft', 'Draft'), ('confirm', 'Confirm'), ('aao_approval', 'AAO Approval'), ('ao_approval', 'AO Approval'), ('ace_approval', 'ACE Approval'), ('adean_approval', 'Assistant Dean Approval'), ('dean_approval', 'Dean Approval'), ('ao_approval_2', 'AO Approval 2'), ('approved', 'Approved')], 'Status', readonly=True) submit_date = fields.Date( 'Submit Date', states={'readonly': ~Eval('state').in_([ 'draft', 'confirm', ])}) approved_date = fields.Date( 'Approved Date', states={'readonly': Eval('state').in_(['approved'])}) center = fields.Many2One('exam.centers', 'Center') designation = fields.Many2One( 'employee.designation', 'Designation', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) department = fields.Many2One( 'company.department', 'Department', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) purpose = fields.Char('Purpose of travel', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state'], required=True) journey = fields.One2Many( 'exam_section.ta_da.journey', 'ta_da', 'Journey', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) hotel_food = fields.One2Many( 'exam_section.ta_da.hotel_food', 'ta_da', 'Hotel/Food', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) local_transport = fields.One2Many( 'exam_section.ta_da.local_transport', 'ta_da', 'Local Transport', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) total_journey = fields.Function(fields.Float('Total Journey Amount'), 'get_total_journey_amount') total_hotel_food = fields.Function(fields.Float('Total Hotel/Food Amount'), 'get_total_hotel_food_amount') total_local_transport = fields.Function( fields.Float('Total Local Transport Amount'), 'get_total_local_transport_amount') total_amount = fields.Function(fields.Float('Total Amount'), 'get_total_amount') recovery = fields.Function(fields.Float('Recovery'), 'calculate_recovery') net_paid = fields.Function(fields.Float('Net Paid'), 'calculate_net_paid') # signatures = fields.One2Many( # 'exam_section.ta_da_signature', # 'ta_da', # 'Signature', # states={ # 'readonly': ~Eval('state').in_(['draft']) # }, # depends=['state'] # ) exam = fields.Many2One('exam_section.exam', 'Exam', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state'], required=True) # @fields.depends('employee') # def on_change_with_designation(self): # return self.employee.designation.name if self.employee else '' # @fields.depends('employee') # def on_change_with_department(self): # return self.employee.department.name if self.employee else '' @property def total_hotel_amount(self): res = 0 if self.hotel_food: for record in self.hotel_food: if record.type_ in ['hotel']: res += record.amount return res @property def total_food_amount(self): res = 0 if self.hotel_food: for record in self.hotel_food: if record.type_ in ['food']: res += record.amount return res @staticmethod def default_state(): return 'draft' @classmethod def __setup__(cls): '''Setup workflow transitions and button properties when an instance of this class is initialized''' super().__setup__() cls._buttons.update({ 'submit': { 'invisible': ~Eval('state').in_(['draft']) }, 'send_for_aao_approval': { 'invisible': ~Eval('state').in_(['confirm']) }, 'send_for_ao_approval': { 'invisible': ~Eval('state').in_(['aao_approval']) }, 'send_for_ace_approval': { 'invisible': ~Eval('state').in_(['ao_approval']) }, 'send_for_adean_approval': { 'invisible': ~Eval('state').in_(['ace_approval']) }, 'send_for_dean_approval': { 'invisible': ~Eval('state').in_(['adean_approval']) }, 'send_for_ao_approval_2': { 'invisible': ~Eval('state').in_(['dean_approval']) }, 'approve': { 'invisible': ~Eval('state').in_(['ao_approval_2']) } }) cls._transitions = set( (('draft', 'confirm'), ('confirm', 'aao_approval'), ('aao_approval', 'ao_approval'), ('ao_approval', 'ace_approval'), ('ace_approval', 'adean_approval'), ('adean_approval', 'dean_approval'), ('dean_approval', 'ao_approval_2'), ('ao_approval_2', 'approved'))) def get_total_journey_amount(self, name): '''Calculate the total amount for journey entered in TA/DA Bill by employee''' total = 0 if self.journey: for journey in self.journey: total += journey.amount return total def get_total_hotel_food_amount(self, name): '''Calculate the total amount for hotel and food entered in TA/DA Bill by employee''' total = 0 if self.hotel_food: for record in self.hotel_food: total += record.amount return total def get_total_local_transport_amount(self, name): '''Calculate the total amount for hotel and food entered in Hotel/Food section of TA/DA Bill by employee''' total = 0 if self.local_transport: for record in self.local_transport: total += record.amount return total def get_total_amount(self, name): '''Calculate the total amount of TA/DA Bill''' return (self.total_journey + self.total_hotel_food + self.total_local_transport) def calculate_recovery(self, name): '''Calculate the recovery for hotel stay, food and journey entered in TA/DA Bill by employee''' global days_hotel global days_food global hotel_entitlement global food_entitlement days_hotel = 0 days_food = 0 hotel_entitlement = 0 food_entitlement = 0 res = 0 ta_da_lines = self.exam.exam_type.ta_da employee_grade_pay = float(self.employee.grade_pay.name) for line in ta_da_lines: if self.employee.employee_group == line.group: for grade_pay in line.grade_pays: if employee_grade_pay >= float(grade_pay.grade_pay.name): hotel_entitlement = line.hotel_charges food_entitlement = line.food_charges for hotel_food in self.hotel_food: days_hotel += hotel_food.no_of_nights_stayed \ if hotel_food.type_ in ['hotel'] else 0 days_food += hotel_food.no_of_days_food \ if hotel_food.type_ in ['food'] else 0 total_hotel = days_hotel * hotel_entitlement total_food = days_food * food_entitlement hotel_amount = self.total_hotel_amount food_amount = self.total_food_amount recovery_hotel = total_hotel - hotel_amount \ if total_hotel > hotel_amount else 0 recovery_food = total_food - food_amount \ if total_food > food_amount else 0 res = recovery_food + recovery_hotel return res def calculate_net_paid(self, name): '''Calculate net paid amount, i.e. total amount - recovery amount''' return self.total_amount - self.recovery @property def total_hotel_amount(self): '''Calculate the total amount for hotel stay entered in Hotel/Food section of TA/DA Bill by employee''' res = 0 if self.hotel_food: for record in self.hotel_food: if record.type_ in ['hotel']: res += record.amount return res @property def total_food_amount(self): '''Calculate the total amount for food entries entered in Hotel/Food section of TA/DA Bill by employee''' res = 0 if self.hotel_food: for record in self.hotel_food: if record.type_ in ['food']: res += record.amount return res @classmethod @Workflow.transition('confirm') def submit(cls, records): '''Change status of bill to confirm Set submit date when TA/DA Bill is submitted''' for record in records: record.submit_date = date.today() record.save() '''Button functions for executing workflow transitions''' @classmethod @Workflow.transition('aao_approval') def send_for_aao_approval(cls, records): '''Change status of bill to aao_approval''' pass @classmethod @Workflow.transition('ao_approval') def send_for_ao_approval(cls, records): '''Change status of bill to ao_approval''' pass @classmethod @Workflow.transition('ace_approval') def send_for_ace_approval(cls, records): '''Change status of bill to ace_approval''' pass @classmethod @Workflow.transition('adean_approval') def send_for_adean_approval(cls, records): '''Change status of bill to adean_approval''' pass @classmethod @Workflow.transition('dean_approval') def send_for_dean_approval(cls, records): '''Change status of bill to dean_approval''' pass @classmethod @Workflow.transition('ao_approval_2') def send_for_ao_approval_2(cls, records): '''Change status of bill to ao_approval_2''' pass @classmethod @Workflow.transition('approved') def approve(cls, records): '''Set approved date when TA/DA Bill is approved''' for record in records: record.approved_date = date.today() record.save()
class ModelViewStoredChangedValuesTarget(ModelSQL, ModelView): "ModelSQL Stored Changed Values Target" __name__ = 'test.modelview.stored.changed_values.target' name = fields.Char("Name") parent = fields.Many2One('test.modelview.stored.changed_values', "Parent")
class PurchaseRequest(ModelSQL, ModelView): 'Purchase Request' __name__ = 'purchase.request' product = fields.Many2One('product.product', 'Product', required=True, select=True, readonly=True, domain=[('purchasable', '=', True)]) party = fields.Many2One('party.party', 'Party', select=True, states=STATES, depends=DEPENDS) quantity = fields.Float('Quantity', required=True, states=STATES, digits=(16, Eval('uom_digits', 2)), depends=DEPENDS + ['uom_digits']) uom = fields.Many2One('product.uom', 'UOM', required=True, select=True, states=STATES, depends=DEPENDS) uom_digits = fields.Function(fields.Integer('UOM Digits'), 'on_change_with_uom_digits') computed_quantity = fields.Float('Computed Quantity', readonly=True) computed_uom = fields.Many2One('product.uom', 'Computed UOM', readonly=True) purchase_date = fields.Date('Best Purchase Date', readonly=True) supply_date = fields.Date('Expected Supply Date', readonly=True) default_uom_digits = fields.Function(fields.Integer('Default UOM Digits'), 'on_change_with_default_uom_digits') stock_level = fields.Float('Stock at Supply Date', readonly=True, digits=(16, Eval('default_uom_digits', 2)), depends=['default_uom_digits']) warehouse = fields.Many2One('stock.location', "Warehouse", states={ 'required': Eval('warehouse_required', False), }, domain=[('type', '=', 'warehouse')], depends=['warehouse_required'], readonly=True) warehouse_required = fields.Function(fields.Boolean('Warehouse Required'), 'get_warehouse_required') purchase_line = fields.Many2One('purchase.line', 'Purchase Line', readonly=True) purchase = fields.Function( fields.Many2One('purchase.purchase', 'Purchase'), 'get_purchase') company = fields.Many2One('company.company', 'Company', required=True, readonly=True, domain=[ ('id', If(In('company', Eval('context', {})), '=', '!='), Eval('context', {}).get('company', -1)), ]) origin = fields.Reference('Origin', selection='get_origin', readonly=True, required=True) state = fields.Function(fields.Selection([ ('purchased', 'Purchased'), ('done', 'Done'), ('draft', 'Draft'), ('cancel', 'Cancel'), ], 'State'), 'get_state', searcher='search_state') @classmethod def __setup__(cls): super(PurchaseRequest, cls).__setup__() cls._order[0] = ('id', 'DESC') cls._error_messages.update({ 'create_request': ('Purchase requests are only created ' 'by the system.'), 'delete_purchase_line': ('You can not delete purchased ' 'request.'), }) @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') cursor = Transaction().cursor sql_table = cls.__table__() super(PurchaseRequest, cls).__register__(module_name) # Migration from 2.0: empty order point origin is -1 instead of 0 cursor.execute( *sql_table.update(columns=[sql_table.origin], values=['stock.order_point,-1'], where=sql_table.origin == 'stock.order_point,0')) # Migration from 3.6: removing the constraint on the quantity tablehandler = TableHandler(cursor, cls, module_name) tablehandler.drop_constraint('check_purchase_request_quantity') def get_rec_name(self, name): if self.warehouse: return "%s@%s" % (self.product.name, self.warehouse.name) else: return self.product.name @classmethod def search_rec_name(cls, name, clause): res = [] names = clause[2].split('@', 1) res.append(('product.template.name', clause[1], names[0])) if len(names) != 1 and names[1]: res.append(('warehouse', clause[1], names[1])) return res @staticmethod def default_company(): return Transaction().context.get('company') def get_purchase(self, name): if self.purchase_line: return self.purchase_line.purchase.id @property def currency(self): return self.company.currency def get_state(self, name): if self.purchase_line: if self.purchase_line.purchase.state == 'cancel': return 'cancel' elif self.purchase_line.purchase.state == 'done': return 'done' else: return 'purchased' return 'draft' @classmethod def search_state(cls, name, clause): pool = Pool() Purchase = pool.get('purchase.purchase') PurchaseLine = pool.get('purchase.line') request = cls.__table__() purchase_line = PurchaseLine.__table__() purchase = Purchase.__table__() _, operator_, state = clause Operator = fields.SQL_OPERATORS[operator_] state_case = Case((purchase.state == 'cancel', 'cancel'), (purchase.state == 'done', 'done'), (request.purchase_line != Null, 'purchased'), else_='draft') state_query = request.join( purchase_line, type_='LEFT', condition=request.purchase_line == purchase_line.id).join( purchase, type_='LEFT', condition=purchase_line.purchase == purchase.id).select( request.id, where=Operator(state_case, state)) return [('id', 'in', state_query)] def get_warehouse_required(self, name): return self.product.type in ('goods', 'assets') @fields.depends('uom') def on_change_with_uom_digits(self, name=None): if self.uom: return self.uom.digits return 2 @fields.depends('product') def on_change_with_default_uom_digits(self, name=None): if self.product: return self.product.default_uom.digits return 2 @staticmethod def _get_origin(): 'Return the set of Model names for origin Reference' return {'stock.order_point'} @classmethod def get_origin(cls): pool = Pool() IrModel = pool.get('ir.model') models = IrModel.search([ ('model', 'in', list(cls._get_origin())), ]) return [(m.model, m.name) for m in models] @classmethod def generate_requests(cls, products=None, warehouses=None): """ For each product compute the purchase request that must be created today to meet product outputs. If products is specified it will compute the purchase requests for the selected products. If warehouses is specified it will compute the purchase request necessary for the selected warehouses. """ pool = Pool() OrderPoint = pool.get('stock.order_point') Product = pool.get('product.product') Location = pool.get('stock.location') User = pool.get('res.user') company = User(Transaction().user).company if warehouses is None: # fetch warehouses: warehouses = Location.search([ ('type', '=', 'warehouse'), ]) warehouse_ids = [w.id for w in warehouses] # fetch order points order_points = OrderPoint.search([ ('type', '=', 'purchase'), ('company', '=', company.id if company else None), ]) # index them by product product2ops = {} for order_point in order_points: product2ops[(order_point.warehouse_location.id, order_point.product.id)] = order_point if products is None: # fetch goods and assets # ordered by ids to speedup reduce_ids in products_by_location products = Product.search([ ('type', 'in', ['goods', 'assets']), ('consumable', '=', False), ('purchasable', '=', True), ], order=[('id', 'ASC')]) product_ids = [p.id for p in products] # aggregate product by minimum supply date date2products = {} for product in products: min_date, max_date = cls.get_supply_dates(product) date2products.setdefault((min_date, max_date), []).append(product) # compute requests new_requests = [] for dates, dates_products in date2products.iteritems(): min_date, max_date = dates for sub_products in grouped_slice(dates_products): sub_products = list(sub_products) product_ids = [p.id for p in sub_products] with Transaction().set_context(forecast=True, stock_date_end=min_date or datetime.date.max): pbl = Product.products_by_location(warehouse_ids, product_ids, with_childs=True) for warehouse_id in warehouse_ids: min_date_qties = dict((x, pbl.pop((warehouse_id, x), 0)) for x in product_ids) # Search for shortage between min-max shortages = cls.get_shortage(warehouse_id, product_ids, min_date, max_date, min_date_qties=min_date_qties, order_points=product2ops) for product in sub_products: shortage_date, product_quantity = shortages[product.id] if shortage_date is None or product_quantity is None: continue order_point = product2ops.get( (warehouse_id, product.id)) # generate request values request = cls.compute_request(product, warehouse_id, shortage_date, product_quantity, company, order_point) new_requests.append(request) # delete purchase requests without a purchase line products = set(products) reqs = cls.search([ ('purchase_line', '=', None), ('origin', 'like', 'stock.order_point,%'), ]) reqs = [r for r in reqs if r.product in products] cls.delete(reqs) new_requests = cls.compare_requests(new_requests) cls.create_requests(new_requests) @classmethod def create_requests(cls, new_requests): for new_req in new_requests: if new_req.supply_date == datetime.date.max: new_req.supply_date = None if new_req.computed_quantity > 0: new_req.save() @classmethod def compare_requests(cls, new_requests): """ Compare new_requests with already existing request to avoid to re-create existing requests. """ pool = Pool() Uom = pool.get('product.uom') Request = pool.get('purchase.request') requests = Request.search([ ('purchase_line.moves', '=', None), ('purchase_line.purchase.state', '!=', 'cancel'), ('origin', 'like', 'stock.order_point,%'), ]) # Fetch data from existing requests existing_req = {} for request in requests: pline = request.purchase_line # Skip incoherent request if request.product.id != pline.product.id or \ request.warehouse.id != pline.purchase.warehouse.id: continue # Take smallest amount between request and purchase line pline_qty = Uom.compute_qty(pline.unit, pline.quantity, pline.product.default_uom, round=False) quantity = min(request.computed_quantity, pline_qty) existing_req.setdefault( (request.product.id, request.warehouse.id), []).append({ 'supply_date': (request.supply_date or datetime.date.max), 'quantity': quantity, }) for i in existing_req.itervalues(): i.sort(lambda r, s: cmp(r['supply_date'], s['supply_date'])) # Update new requests to take existing requests into account new_requests.sort(key=operator.attrgetter('supply_date')) for new_req in new_requests: for old_req in existing_req.get( (new_req.product.id, new_req.warehouse.id), []): if old_req['supply_date'] <= new_req.supply_date: new_req.computed_quantity = max( 0.0, new_req.computed_quantity - old_req['quantity']) new_req.quantity = Uom.compute_qty( new_req.product.default_uom, new_req.computed_quantity, new_req.uom) old_req['quantity'] = max( 0.0, old_req['quantity'] - new_req.computed_quantity) else: break return new_requests @classmethod def get_supply_dates(cls, product): """ Return the minimal interval of earliest supply dates for a product. """ Date = Pool().get('ir.date') min_date = None max_date = None today = Date.today() for product_supplier in product.product_suppliers: supply_date = product_supplier.compute_supply_date(date=today) # TODO next_day is by default today + 1 but should depends # on the CRON activity next_day = today + datetime.timedelta(1) next_supply_date = product_supplier.compute_supply_date( date=next_day) if (not min_date) or supply_date < min_date: min_date = supply_date if (not max_date): max_date = next_supply_date if supply_date > min_date and supply_date < max_date: max_date = supply_date if next_supply_date < max_date: max_date = next_supply_date if not min_date: min_date = datetime.date.max max_date = datetime.date.max return (min_date, max_date) @classmethod def find_best_supplier(cls, product, date): ''' Return the best supplier and purchase_date for the product. ''' Date = Pool().get('ir.date') supplier = None today = Date.today() for product_supplier in product.product_suppliers: supply_date = product_supplier.compute_supply_date(date=today) timedelta = date - supply_date if not supplier and timedelta >= datetime.timedelta(0): supplier = product_supplier.party break if supplier: purchase_date = product_supplier.compute_purchase_date(date) else: purchase_date = today return supplier, purchase_date @classmethod def compute_request(cls, product, location_id, shortage_date, product_quantity, company, order_point=None): """ Return the value of the purchase request which will answer to the needed quantity at the given date. I.e: the latest purchase date, the expected supply date and the prefered supplier. """ pool = Pool() Uom = pool.get('product.uom') Request = pool.get('purchase.request') supplier, purchase_date = cls.find_best_supplier( product, shortage_date) max_quantity = order_point and order_point.max_quantity or 0.0 computed_quantity = max_quantity - product_quantity quantity = Uom.compute_qty(product.default_uom, computed_quantity, product.purchase_uom or product.default_uom) if order_point: origin = 'stock.order_point,%s' % order_point.id else: origin = 'stock.order_point,-1' return Request( product=product, party=supplier and supplier or None, quantity=quantity, uom=product.purchase_uom or product.default_uom, computed_quantity=computed_quantity, computed_uom=product.default_uom, purchase_date=purchase_date, supply_date=shortage_date, stock_level=product_quantity, company=company, warehouse=location_id, origin=origin, ) @classmethod def get_shortage(cls, location_id, product_ids, min_date, max_date, min_date_qties, order_points): """ Return for each product the first date between min_date and max_date where the stock quantity is less than the minimal quantity and the smallest stock quantity in the interval or None if there is no date where stock quantity is less than the minimal quantity. The minimal quantity comes from the order point or is zero. min_date_qty is the quantities for each products at the min_date. order_points is a dictionary that links products to order point. """ Product = Pool().get('product.product') res_dates = {} res_qties = {} min_quantities = {} for product_id in product_ids: order_point = order_points.get((location_id, product_id)) if order_point: min_quantities[product_id] = order_point.min_quantity else: min_quantities[product_id] = 0.0 current_date = min_date current_qties = min_date_qties.copy() while (current_date < max_date) or (current_date == min_date): for product_id in product_ids: current_qty = current_qties[product_id] min_quantity = min_quantities[product_id] res_qty = res_qties.get(product_id) res_date = res_dates.get(product_id) if current_qty < min_quantity: if not res_date: res_dates[product_id] = current_date if (not res_qty) or (current_qty < res_qty): res_qties[product_id] = current_qty if current_date == datetime.date.max: break current_date += datetime.timedelta(1) # Update current quantities with next moves with Transaction().set_context(forecast=True, stock_date_start=current_date, stock_date_end=current_date): pbl = Product.products_by_location([location_id], product_ids, with_childs=True) for key, qty in pbl.iteritems(): _, product_id = key current_qties[product_id] += qty return dict( (x, (res_dates.get(x), res_qties.get(x))) for x in product_ids) @classmethod def create(cls, vlist): for vals in vlist: for field_name in ('product', 'quantity', 'uom', 'company'): if not vals.get(field_name): cls.raise_user_error('create_request') return super(PurchaseRequest, cls).create(vlist) @classmethod def delete(cls, requests): if any(r.purchase_line for r in requests): cls.raise_user_error('delete_purchase_line') super(PurchaseRequest, cls).delete(requests)
class Journal(metaclass=PoolMeta): __name__ = 'account.payment.journal' company_party = fields.Function( fields.Many2One('party.party', 'Company Party'), 'on_change_with_company_party') sepa_bank_account_number = fields.Many2One( 'bank.account.number', 'Bank Account Number', states={ 'required': Eval('process_method') == 'sepa', 'invisible': Eval('process_method') != 'sepa', }, domain=[ ('type', '=', 'iban'), ('account.owners', '=', Eval('company_party')), ], depends=['process_method', 'company_party']) sepa_payable_flavor = fields.Selection( [ (None, ''), ('pain.001.001.03', 'pain.001.001.03'), ('pain.001.001.05', 'pain.001.001.05'), ('pain.001.003.03', 'pain.001.003.03'), ], 'Payable Flavor', states={ 'required': Eval('process_method') == 'sepa', 'invisible': Eval('process_method') != 'sepa', }, translate=False, depends=['process_method']) sepa_receivable_flavor = fields.Selection( [ (None, ''), ('pain.008.001.02', 'pain.008.001.02'), ('pain.008.001.04', 'pain.008.001.04'), ('pain.008.003.02', 'pain.008.003.02'), ], 'Receivable Flavor', states={ 'required': Eval('process_method') == 'sepa', 'invisible': Eval('process_method') != 'sepa', }, translate=False, depends=['process_method']) sepa_batch_booking = fields.Boolean('Batch Booking', states={ 'invisible': Eval('process_method') != 'sepa', }, depends=['process_method']) sepa_charge_bearer = fields.Selection( [ ('DEBT', 'Debtor'), ('CRED', 'Creditor'), ('SHAR', 'Shared'), ('SLEV', 'Service Level'), ], 'Charge Bearer', states={ 'required': Eval('process_method') == 'sepa', 'invisible': Eval('process_method') != 'sepa', }, depends=['process_method']) @classmethod def __setup__(cls): super(Journal, cls).__setup__() sepa_method = ('sepa', 'SEPA') if sepa_method not in cls.process_method.selection: cls.process_method.selection.append(sepa_method) @classmethod def default_company_party(cls): pool = Pool() Company = pool.get('company.company') company_id = cls.default_company() if company_id: return Company(company_id).party.id @fields.depends('company') def on_change_with_company_party(self, name=None): if self.company: return self.company.party.id @staticmethod def default_sepa_charge_bearer(): return 'SLEV'
class Message(Workflow, ModelSQL, ModelView): 'SEPA Message' __name__ = 'account.payment.sepa.message' _states = { 'readonly': Eval('state') != 'draft', } _depends = ['state'] message = fields.Text('Message', states=_states, depends=_depends) filename = fields.Function(fields.Char('Filename'), 'get_filename') type = fields.Selection([ ('in', 'IN'), ('out', 'OUT'), ], 'Type', required=True, states=_states, depends=_depends) company = fields.Many2One( 'company.company', 'Company', required=True, select=True, domain=[ ('id', If(Eval('context', {}).contains('company'), '=', '!='), Eval('context', {}).get('company', -1)), ], states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) origin = fields.Reference('Origin', selection='get_origin', select=True, states=_states, depends=_depends) state = fields.Selection([ ('draft', 'Draft'), ('waiting', 'Waiting'), ('done', 'Done'), ('canceled', 'Canceled'), ], 'State', readonly=True, select=True) @classmethod def __setup__(cls): super(Message, cls).__setup__() cls._transitions |= { ('draft', 'waiting'), ('waiting', 'done'), ('waiting', 'draft'), ('draft', 'canceled'), ('waiting', 'canceled'), } cls._buttons.update({ 'cancel': { 'invisible': ~Eval('state').in_(['draft', 'waiting']), 'depends': ['state'], }, 'draft': { 'invisible': Eval('state') != 'waiting', 'depends': ['state'], }, 'wait': { 'invisible': Eval('state') != 'draft', 'depends': ['state'], }, 'do': { 'invisible': Eval('state') != 'waiting', 'depends': ['state'], }, }) @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') cursor = Transaction().connection.cursor() pool = Pool() Group = pool.get('account.payment.group') super(Message, cls).__register__(module_name) # Migration from 3.2 if TableHandler.table_exist(Group._table): group_table = Group.__table_handler__(module_name) if group_table.column_exist('sepa_message'): group = Group.__table__() table = cls.__table__() cursor.execute( *group.select(group.id, group.sepa_message, group.company)) for group_id, message, company_id in cursor.fetchall(): cursor.execute(*table.insert([ table.message, table.type, table.company, table.origin, table.state ], [[ message, 'out', company_id, 'account.payment.group,%s' % group_id, 'done' ]])) group_table.drop_column('sepa_message') @staticmethod def default_type(): return 'in' @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_state(): return 'draft' def get_filename(self, name): pool = Pool() Group = pool.get('account.payment.group') if isinstance(self.origin, Group): return self.origin.rec_name + '.xml' @staticmethod def _get_origin(): 'Return list of Model names for origin Reference' return ['account.payment.group'] @classmethod def get_origin(cls): IrModel = Pool().get('ir.model') models = cls._get_origin() models = IrModel.search([ ('model', 'in', models), ]) return [(None, '')] + [(m.model, m.name) for m in models] @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, messages): pass @classmethod @ModelView.button @Workflow.transition('waiting') def wait(cls, messages): pass @classmethod @ModelView.button @Workflow.transition('done') def do(cls, messages): for message in messages: if message.type == 'in': message.parse() else: message.send() @classmethod @ModelView.button @Workflow.transition('canceled') def cancel(cls, messages): pass @staticmethod def _get_handlers(): pool = Pool() Payment = pool.get('account.payment') return { 'urn:iso:std:iso:20022:tech:xsd:camt.054.001.01': lambda f: CAMT054(f, Payment), 'urn:iso:std:iso:20022:tech:xsd:camt.054.001.02': lambda f: CAMT054(f, Payment), 'urn:iso:std:iso:20022:tech:xsd:camt.054.001.03': lambda f: CAMT054(f, Payment), 'urn:iso:std:iso:20022:tech:xsd:camt.054.001.04': lambda f: CAMT054(f, Payment), } @staticmethod def get_namespace(message): f = BytesIO(message) for _, element in etree.iterparse(f, events=('start', )): tag = etree.QName(element) if tag.localname == 'Document': return tag.namespace def parse(self): message = self.message.encode('utf-8') f = BytesIO(message) namespace = self.get_namespace(message) handlers = self._get_handlers() if namespace not in handlers: raise # TODO UserError handlers[namespace](f) def send(self): pass
class Mandate(Workflow, ModelSQL, ModelView): 'SEPA Mandate' __name__ = 'account.payment.sepa.mandate' party = fields.Many2One('party.party', 'Party', required=True, select=True, states={ 'readonly': Eval('state').in_( ['requested', 'validated', 'canceled']), }, depends=['state']) account_number = fields.Many2One( 'bank.account.number', 'Account Number', ondelete='RESTRICT', states={ 'readonly': Eval('state').in_(['validated', 'canceled']), 'required': Eval('state') == 'validated', }, domain=[ ('type', '=', 'iban'), ('account.owners', '=', Eval('party')), ], depends=['state', 'party']) identification = fields.Char('Identification', size=35, states={ 'readonly': Eval('identification_readonly', True), 'required': Eval('state') == 'validated', }, depends=['state', 'identification_readonly']) identification_readonly = fields.Function( fields.Boolean('Identification Readonly'), 'get_identification_readonly') company = fields.Many2One( 'company.company', 'Company', required=True, select=True, domain=[ ('id', If(Eval('context', {}).contains('company'), '=', '!='), Eval('context', {}).get('company', -1)), ], states={ 'readonly': Eval('state') != 'draft', }, depends=['state']) type = fields.Selection([ ('recurrent', 'Recurrent'), ('one-off', 'One-off'), ], 'Type', states={ 'readonly': Eval('state').in_(['validated', 'canceled']), }, depends=['state']) sequence_type_rcur = fields.Boolean("Always use RCUR", states={ 'invisible': Eval('type') == 'one-off', }, depends=['type']) scheme = fields.Selection([ ('CORE', 'Core'), ('B2B', 'Business to Business'), ], 'Scheme', required=True, states={ 'readonly': Eval('state').in_(['validated', 'canceled']), }, depends=['state']) scheme_string = scheme.translated('scheme') signature_date = fields.Date('Signature Date', states={ 'readonly': Eval('state').in_( ['validated', 'canceled']), 'required': Eval('state') == 'validated', }, depends=['state']) state = fields.Selection([ ('draft', 'Draft'), ('requested', 'Requested'), ('validated', 'Validated'), ('canceled', 'Canceled'), ], 'State', readonly=True) payments = fields.One2Many('account.payment', 'sepa_mandate', 'Payments') has_payments = fields.Function(fields.Boolean('Has Payments'), 'has_payments') @classmethod def __setup__(cls): super(Mandate, cls).__setup__() cls._transitions |= set(( ('draft', 'requested'), ('requested', 'validated'), ('validated', 'canceled'), ('requested', 'canceled'), ('requested', 'draft'), )) cls._buttons.update({ 'cancel': { 'invisible': ~Eval('state').in_(['requested', 'validated']), 'depends': ['state'], }, 'draft': { 'invisible': Eval('state') != 'requested', 'depends': ['state'], }, 'request': { 'invisible': Eval('state') != 'draft', 'depends': ['state'], }, 'validate_mandate': { 'invisible': Eval('state') != 'requested', 'depends': ['state'], }, }) # t = cls.__table__() # JMO/RSE 2017_04_18 Following 4929e02594d # We override in coog the possibility to keep the mandate # for several bank account but we suffer from the register order # if we try to delete the constraint from coog module # t = cls.__table__() # cls._sql_constraints = [ # ('identification_unique', Unique(t, t.company, t.identification), # 'account_payment_sepa.msg_mandate_unique_id'), # ] @staticmethod def default_company(): return Transaction().context.get('company') @staticmethod def default_type(): return 'recurrent' @classmethod def default_sequence_type_rcur(cls): return False @staticmethod def default_scheme(): return 'CORE' @staticmethod def default_state(): return 'draft' @staticmethod def default_identification_readonly(): pool = Pool() Configuration = pool.get('account.configuration') config = Configuration(1) return bool(config.sepa_mandate_sequence) def get_identification_readonly(self, name): return bool(self.identification) def get_rec_name(self, name): if self.identification: return self.identification return '(%s)' % self.id @classmethod def search_rec_name(cls, name, clause): return [tuple(('identification', )) + tuple(clause[1:])] @classmethod def create(cls, vlist): pool = Pool() Sequence = pool.get('ir.sequence') Configuration = pool.get('account.configuration') config = Configuration(1) vlist = [v.copy() for v in vlist] for values in vlist: if (config.sepa_mandate_sequence and not values.get('identification')): values['identification'] = Sequence.get_id( config.sepa_mandate_sequence.id) # Prevent raising false unique constraint if values.get('identification') == '': values['identification'] = None return super(Mandate, cls).create(vlist) @classmethod def write(cls, *args): actions = iter(args) args = [] for mandates, values in zip(actions, actions): # Prevent raising false unique constraint if values.get('identification') == '': values = values.copy() values['identification'] = None args.extend((mandates, values)) super(Mandate, cls).write(*args) @classmethod def copy(cls, mandates, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('payments', []) default.setdefault('signature_date', None) default.setdefault('identification', None) return super(Mandate, cls).copy(mandates, default=default) @property def is_valid(self): if self.state == 'validated': if self.type == 'one-off': if not self.has_payments: return True else: return True return False @property def sequence_type(self): if self.type == 'one-off': return 'OOFF' elif not self.sequence_type_rcur and (not self.payments or all( not p.sepa_mandate_sequence_type for p in self.payments) or all(p.rejected for p in self.payments)): return 'FRST' # TODO manage FNAL else: return 'RCUR' @classmethod def has_payments(cls, mandates, name): pool = Pool() Payment = pool.get('account.payment') payment = Payment.__table__ cursor = Transaction().connection.cursor() has_payments = dict.fromkeys([m.id for m in mandates], False) for sub_ids in grouped_slice(mandates): red_sql = reduce_ids(payment.sepa_mandate, sub_ids) cursor.execute(*payment.select(payment.sepa_mandate, Literal(True), where=red_sql, group_by=payment.sepa_mandate)) has_payments.update(cursor.fetchall()) return {'has_payments': has_payments} @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, mandates): pass @classmethod @ModelView.button @Workflow.transition('requested') def request(cls, mandates): pass @classmethod @ModelView.button @Workflow.transition('validated') def validate_mandate(cls, mandates): pass @classmethod @ModelView.button @Workflow.transition('canceled') def cancel(cls, mandates): # TODO must be automaticaly canceled 13 months after last collection pass @classmethod def delete(cls, mandates): for mandate in mandates: if mandate.state not in ('draft', 'canceled'): raise AccessError( gettext( 'account_payment_sepa' '.msg_mandate_delete_draft_canceled', mandate=mandate.rec_name)) super(Mandate, cls).delete(mandates)
class Payment(metaclass=PoolMeta): __name__ = 'account.payment' sepa_mandate = fields.Many2One('account.payment.sepa.mandate', 'Mandate', ondelete='RESTRICT', domain=[ ('party', '=', Eval('party', -1)), ('company', '=', Eval('company', -1)), ], depends=['party', 'company']) sepa_mandate_sequence_type = fields.Char('Mandate Sequence Type', readonly=True) sepa_return_reason_code = fields.Char('Return Reason Code', readonly=True, states={ 'invisible': (~Eval('sepa_return_reason_code') & (Eval('state') != 'failed')), }, depends=['state']) sepa_return_reason_information = fields.Text( 'Return Reason Information', readonly=True, states={ 'invisible': (~Eval('sepa_return_reason_information') & (Eval('state') != 'failed')), }, depends=['state']) sepa_end_to_end_id = fields.Function(fields.Char('SEPA End To End ID'), 'get_sepa_end_to_end_id', searcher='search_end_to_end_id') sepa_instruction_id = fields.Function( fields.Char('SEPA Instruction ID'), 'get_sepa_instruction_id', searcher='search_sepa_instruction_id') @classmethod def copy(cls, payments, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('sepa_mandate_sequence_type', None) return super(Payment, cls).copy(payments, default=default) @classmethod def get_sepa_mandates(cls, payments): mandates = [] for payment in payments: if payment.sepa_mandate: if payment.sepa_mandate.is_valid: mandate = payment.sepa_mandate else: mandate = None else: for mandate in payment.party.sepa_mandates: if mandate.is_valid: break else: mandate = None mandates.append(mandate) return mandates def get_sepa_end_to_end_id(self, name): return str(self.id) @classmethod def search_end_to_end_id(cls, name, domain): table = cls.__table__() _, operator, value = domain cast = cls.sepa_end_to_end_id._field.sql_type().base Operator = fields.SQL_OPERATORS[operator] query = table.select(table.id, where=Operator(table.id.cast(cast), value)) return [('id', 'in', query)] get_sepa_instruction_id = get_sepa_end_to_end_id search_sepa_instruction_id = search_end_to_end_id @property def sepa_remittance_information(self): if self.description: return self.description elif self.line and self.line.move_origin: return self.line.move_origin.rec_name @property def sepa_bank_account_number(self): if self.kind == 'receivable': if self.sepa_mandate: return self.sepa_mandate.account_number else: for account in self.party.bank_accounts: for number in account.numbers: if number.type == 'iban': return number @property def rejected(self): return (self.state == 'failed' and self.sepa_return_reason_code and self.sepa_return_reason_information == '/RTYP/RJCT') def create_clearing_move(self, date=None): if not date: date = Transaction().context.get('date_value') return super(Payment, self).create_clearing_move(date=date)
class Party: __metaclass__ = PoolMeta __name__ = 'party.party' customer_payment_type = fields.Function(fields.Many2One( 'account.payment.type', 'Customer Payment type', domain=[ ('kind', '=', 'receivable'), ], help='Payment type of the customer.'), 'get_payment_type', setter='set_payment_type', searcher='search_payment_type') supplier_payment_type = fields.Function(fields.Many2One( 'account.payment.type', string='Supplier Payment type', domain=[ ('kind', '=', 'payable'), ], help='Payment type of the supplier.'), 'get_payment_type', setter='set_payment_type', searcher='search_payment_type') @classmethod def get_payment_type(cls, parties, names): PartyAccountPaymentType = Pool().get('party.account.payment.type') company = Transaction().context.get('company') res = {} party_ids = [p.id for p in parties] for fname in names: res[fname] = {}.fromkeys(party_ids, None) party_account_payment_type = PartyAccountPaymentType.search([ ('company', '=', company), ('party', 'in', parties) ]) for payment_type_fields in party_account_payment_type: party_id = payment_type_fields.party.id for fname in set(names): value = getattr(payment_type_fields, fname) res[fname][party_id] = value and value.id return res @classmethod def set_payment_type(cls, parties, name, value): PartyAccountPaymentType = Pool().get('party.account.payment.type') company = Transaction().context.get('company') party_payment_types = PartyAccountPaymentType.search([ ('company', '=', company), ('party', 'in', parties) ]) PartyAccountPaymentType.write(party_payment_types, {name: value}) vlist = [] parties_done_ids = [papt.party.id for papt in party_payment_types] for missing_party_id in set(p.id for p in parties if p.id not in parties_done_ids): vlist.append({ 'party': missing_party_id, 'company': company, name: value, }) if vlist: PartyAccountPaymentType.create(vlist) @classmethod def search_payment_type(cls, name, clause): PartyAccountPaymentType = Pool().get('party.account.payment.type') company = Transaction().context.get('company') party_payment_types = PartyAccountPaymentType.search([ ('company', '=', company), tuple(clause), ]) return [('id', 'in', [papt.party.id for papt in party_payment_types])]
class Surgery(ModelSQL, ModelView): 'Surgery' __name__ = 'gnuhealth.surgery' def surgery_duration(self, name): if (self.surgery_end_date and self.surgery_date): return self.surgery_end_date - self.surgery_date else: return None def patient_age_at_surgery(self, name): if (self.patient.name.dob and self.surgery_date): rdelta = relativedelta(self.surgery_date.date(), self.patient.name.dob) years_months_days = str(rdelta.years) + 'y ' \ + str(rdelta.months) + 'm ' \ + str(rdelta.days) + 'd' return years_months_days else: return None patient = fields.Many2One('gnuhealth.patient', 'Patient', required=True) admission = fields.Many2One('gnuhealth.appointment', 'Admission') operating_room = fields.Many2One('gnuhealth.hospital.or', 'Operating Room') code = fields.Char('Code', readonly=True, help="Health Center code / sequence") procedures = fields.One2Many( 'gnuhealth.operation', 'name', 'Procedures', help="List of the procedures in the surgery. Please enter the first " "one as the main procedure") supplies = fields.One2Many( 'gnuhealth.surgery_supply', 'name', 'Supplies', help="List of the supplies required for the surgery") pathology = fields.Many2One('gnuhealth.pathology', 'Condition', help="Base Condition / Reason") classification = fields.Selection([ (None, ''), ('o', 'Optional'), ('r', 'Required'), ('u', 'Urgent'), ('e', 'Emergency'), ], 'Urgency', help="Urgency level for this surgery", sort=False) surgeon = fields.Many2One('gnuhealth.healthprofessional', 'Surgeon', help="Surgeon who did the procedure") anesthetist = fields.Many2One('gnuhealth.healthprofessional', 'Anesthetist', help="Anesthetist in charge") surgery_date = fields.DateTime('Date', help="Start of the Surgery") surgery_end_date = fields.DateTime( 'End', states={ 'required': Equal(Eval('state'), 'done'), }, help="Automatically set when the surgery is done." "It is also the estimated end time when confirming the surgery.") surgery_length = fields.Function( fields.TimeDelta('Duration', states={ 'invisible': And(Not(Equal(Eval('state'), 'done')), Not(Equal(Eval('state'), 'signed'))) }, help="Length of the surgery"), 'surgery_duration') state = fields.Selection([ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('cancelled', 'Cancelled'), ('in_progress', 'In Progress'), ('done', 'Done'), ('signed', 'Signed'), ], 'State', readonly=True, sort=False) signed_by = fields.Many2One( 'gnuhealth.healthprofessional', 'Signed by', readonly=True, states={'invisible': Not(Equal(Eval('state'), 'signed'))}, help="Health Professional that signed this surgery document") # age is deprecated in GNU Health 2.0 age = fields.Char('Estimative Age', help="Use this field for historical purposes, \ when no date of surgery is given") computed_age = fields.Function( fields.Char('Age', help="Computed patient age at the moment of the surgery"), 'patient_age_at_surgery') gender = fields.Function(fields.Selection([ (None, ''), ('m', 'Male'), ('f', 'Female'), ('f-m', 'Female -> Male'), ('m-f', 'Male -> Female'), ], 'Gender'), 'get_patient_gender', searcher='search_patient_gender') description = fields.Char('Description') preop_mallampati = fields.Selection([ (None, ''), ('Class 1', 'Class 1: Full visibility of tonsils, uvula and soft ' 'palate'), ('Class 2', 'Class 2: Visibility of hard and soft palate, ' 'upper portion of tonsils and uvula'), ('Class 3', 'Class 3: Soft and hard palate and base of the uvula are ' 'visible'), ('Class 4', 'Class 4: Only Hard Palate visible'), ], 'Mallampati Score', sort=False) preop_bleeding_risk = fields.Boolean( 'Risk of Massive bleeding', help="Patient has a risk of losing more than 500 " "ml in adults of over 7ml/kg in infants. If so, make sure that " "intravenous access and fluids are available") preop_oximeter = fields.Boolean('Pulse Oximeter in place', help="Pulse oximeter is in place " "and functioning") preop_site_marking = fields.Boolean( 'Surgical Site Marking', help="The surgeon has marked the surgical incision") preop_antibiotics = fields.Boolean( 'Antibiotic Prophylaxis', help="Prophylactic antibiotic treatment within the last 60 minutes") preop_sterility = fields.Boolean( 'Sterility confirmed', help="Nursing team has confirmed sterility of the devices and room") preop_asa = fields.Selection([ (None, ''), ('ps1', 'PS 1 : Normal healthy patient'), ('ps2', 'PS 2 : Patients with mild systemic disease'), ('ps3', 'PS 3 : Patients with severe systemic disease'), ('ps4', 'PS 4 : Patients with severe systemic disease that is' ' a constant threat to life '), ('ps5', 'PS 5 : Moribund patients who are not expected to' ' survive without the operation'), ('ps6', 'PS 6 : A declared brain-dead patient who organs are' ' being removed for donor purposes'), ], 'ASA PS', help="ASA pre-operative Physical Status", sort=False) preop_rcri = fields.Many2One( 'gnuhealth.rcri', 'RCRI', help='Patient Revised Cardiac Risk Index\n' 'Points 0: Class I Very Low (0.4% complications)\n' 'Points 1: Class II Low (0.9% complications)\n' 'Points 2: Class III Moderate (6.6% complications)\n' 'Points 3 or more : Class IV High (>11% complications)') surgical_wound = fields.Selection([ (None, ''), ('I', 'Clean . Class I'), ('II', 'Clean-Contaminated . Class II'), ('III', 'Contaminated . Class III'), ('IV', 'Dirty-Infected . Class IV'), ], 'Surgical wound', sort=False) extra_info = fields.Text('Extra Info') anesthesia_report = fields.Text('Anesthesia Report') institution = fields.Many2One('gnuhealth.institution', 'Institution') report_surgery_date = fields.Function(fields.Date('Surgery Date'), 'get_report_surgery_date') report_surgery_time = fields.Function(fields.Time('Surgery Time'), 'get_report_surgery_time') surgery_team = fields.One2Many( 'gnuhealth.surgery_team', 'name', 'Team Members', help="Professionals Involved in the surgery") postoperative_dx = fields.Many2One( 'gnuhealth.pathology', 'Post-op dx', states={ 'invisible': And(Not(Equal(Eval('state'), 'done')), Not(Equal(Eval('state'), 'signed'))) }, help="Post-operative diagnosis") @staticmethod def default_institution(): HealthInst = Pool().get('gnuhealth.institution') institution = HealthInst.get_institution() return institution @staticmethod def default_surgery_date(): return datetime.now() @staticmethod def default_surgeon(): pool = Pool() HealthProf = pool.get('gnuhealth.healthprofessional') surgeon = HealthProf.get_health_professional() return surgeon @staticmethod def default_state(): return 'draft' def get_patient_gender(self, name): return self.patient.gender @classmethod def search_patient_gender(cls, name, clause): res = [] value = clause[2] res.append(('patient.name.gender', clause[1], value)) return res # Show the gender and age upon entering the patient # These two are function fields (don't exist at DB level) @fields.depends('patient') def on_change_patient(self): gender = None age = '' self.gender = self.patient.gender self.computed_age = self.patient.age @classmethod def create(cls, vlist): Sequence = Pool().get('ir.sequence') Config = Pool().get('gnuhealth.sequences') vlist = [x.copy() for x in vlist] for values in vlist: if not values.get('code'): config = Config(1) values['code'] = Sequence.get_id( config.surgery_code_sequence.id) return super(Surgery, cls).create(vlist) @classmethod # Update to version 2.0 def __register__(cls, module_name): cursor = Transaction().cursor TableHandler = backend.get('TableHandler') table = TableHandler(cursor, cls, module_name) # Rename the date column to surgery_surgery_date if table.column_exist('date'): table.column_rename('date', 'surgery_date') super(Surgery, cls).__register__(module_name) @classmethod def __setup__(cls): super(Surgery, cls).__setup__() cls._error_messages.update({ 'end_date_before_start': 'End time "%(end_date)s" BEFORE ' 'surgery date "%(surgery_date)s"', 'or_is_not_available': 'Operating Room is not available' }) cls._buttons.update({ 'confirmed': { 'invisible': And(Not(Equal(Eval('state'), 'draft')), Not(Equal(Eval('state'), 'cancelled'))), }, 'cancel': { 'invisible': Not(Equal(Eval('state'), 'confirmed')), }, 'start': { 'invisible': Not(Equal(Eval('state'), 'confirmed')), }, 'done': { 'invisible': Not(Equal(Eval('state'), 'in_progress')), }, 'signsurgery': { 'invisible': Not(Equal(Eval('state'), 'done')), }, }) @classmethod def validate(cls, surgeries): super(Surgery, cls).validate(surgeries) for surgery in surgeries: surgery.validate_surgery_period() def validate_surgery_period(self): Lang = Pool().get('ir.lang') language, = Lang.search([ ('code', '=', Transaction().language), ]) if (self.surgery_end_date and self.surgery_date): if (self.surgery_end_date < self.surgery_date): self.raise_user_error( 'end_date_before_start', { 'surgery_date': Lang.strftime(self.surgery_date, language.code, language.date), 'end_date': Lang.strftime(self.surgery_end_date, language.code, language.date), }) @classmethod def write(cls, surgeries, vals): # Don't allow to write the record if the surgery has been signed if surgeries[0].state == 'signed': cls.raise_user_error( "This surgery is at state Done and has been signed\n" "You can no longer modify it.") return super(Surgery, cls).write(surgeries, vals) ## Method to check for availability and make the Operating Room reservation # for the associated surgery @classmethod @ModelView.button def confirmed(cls, surgeries): surgery_id = surgeries[0] Operating_room = Pool().get('gnuhealth.hospital.or') cursor = Transaction().cursor # Operating Room and end surgery time check if (not surgery_id.operating_room or not surgery_id.surgery_end_date): cls.raise_user_error("Operating Room and estimated end time " "are needed in order to confirm the surgery") or_id = surgery_id.operating_room.id cursor.execute( "SELECT COUNT(*) \ FROM gnuhealth_surgery \ WHERE (surgery_date::timestamp,surgery_end_date::timestamp) \ OVERLAPS (timestamp %s, timestamp %s) \ AND (state = %s or state = %s) \ AND operating_room = CAST(%s AS INTEGER) ", (surgery_id.surgery_date, surgery_id.surgery_end_date, 'confirmed', 'in_progress', str(or_id))) res = cursor.fetchone() if (surgery_id.surgery_end_date < surgery_id.surgery_date): cls.raise_user_error("The Surgery end date must later than the \ Start") if res[0] > 0: cls.raise_user_error('or_is_not_available') else: cls.write(surgeries, {'state': 'confirmed'}) # Cancel the surgery and set it to draft state # Free the related Operating Room @classmethod @ModelView.button def cancel(cls, surgeries): surgery_id = surgeries[0] Operating_room = Pool().get('gnuhealth.hospital.or') cls.write(surgeries, {'state': 'cancelled'}) # Start the surgery @classmethod @ModelView.button def start(cls, surgeries): surgery_id = surgeries[0] Operating_room = Pool().get('gnuhealth.hospital.or') cls.write( surgeries, { 'state': 'in_progress', 'surgery_date': datetime.now(), 'surgery_end_date': datetime.now() }) Operating_room.write([surgery_id.operating_room], {'state': 'occupied'}) # Finnish the surgery # Free the related Operating Room @classmethod @ModelView.button def done(cls, surgeries): surgery_id = surgeries[0] Operating_room = Pool().get('gnuhealth.hospital.or') cls.write(surgeries, { 'state': 'done', 'surgery_end_date': datetime.now() }) Operating_room.write([surgery_id.operating_room], {'state': 'free'}) # Sign the surgery document, and the surgical act. @classmethod @ModelView.button def signsurgery(cls, surgeries): surgery_id = surgeries[0] # Sign, change the state of the Surgery to "Signed" # and write the name of the signing health professional signing_hp = Pool().get( 'gnuhealth.healthprofessional').get_health_professional() if not signing_hp: cls.raise_user_error( "No health professional associated to this user !") cls.write(surgeries, {'state': 'signed', 'signed_by': signing_hp}) def get_report_surgery_date(self, name): Company = Pool().get('company.company') timezone = None company_id = Transaction().context.get('company') if company_id: company = Company(company_id) if company.timezone: timezone = pytz.timezone(company.timezone) dt = self.surgery_date return datetime.astimezone(dt.replace(tzinfo=pytz.utc), timezone).date() def get_report_surgery_time(self, name): Company = Pool().get('company.company') timezone = None company_id = Transaction().context.get('company') if company_id: company = Company(company_id) if company.timezone: timezone = pytz.timezone(company.timezone) dt = self.surgery_date return datetime.astimezone(dt.replace(tzinfo=pytz.utc), timezone).time()
class CreatePurchaseAskParty(ModelView): 'Create Purchase Ask Party' __name__ = 'purchase.request.create_purchase.ask_party' product = fields.Many2One('product.product', 'Product', readonly=True) company = fields.Many2One('company.company', 'Company', readonly=True) party = fields.Many2One('party.party', 'Supplier', required=True)
class RCRI(ModelSQL, ModelView): 'Revised Cardiac Risk Index' __name__ = 'gnuhealth.rcri' patient = fields.Many2One('gnuhealth.patient', 'Patient ID', required=True) rcri_date = fields.DateTime('Date', required=True) health_professional = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Professional', help="Health professional /" "Cardiologist who signed the assesment RCRI") rcri_high_risk_surgery = fields.Boolean( 'High Risk surgery', help='Includes andy suprainguinal vascular, intraperitoneal,' ' or intrathoracic procedures') rcri_ischemic_history = fields.Boolean( 'History of ischemic heart disease', help="history of MI or a positive exercise test, current \ complaint of chest pain considered to be secondary to myocardial \ ischemia, use of nitrate therapy, or ECG with pathological \ Q waves; do not count prior coronary revascularization procedure \ unless one of the other criteria for ischemic heart disease is \ present") rcri_congestive_history = fields.Boolean( 'History of congestive heart disease') rcri_diabetes_history = fields.Boolean( 'Preoperative Diabetes', help="Diabetes Mellitus requiring treatment with Insulin") rcri_cerebrovascular_history = fields.Boolean( 'History of Cerebrovascular disease') rcri_kidney_history = fields.Boolean( 'Preoperative Kidney disease', help="Preoperative serum creatinine >2.0 mg/dL (177 mol/L)") rcri_total = fields.Integer( 'Score', help='Points 0: Class I Very Low (0.4% complications)\n' 'Points 1: Class II Low (0.9% complications)\n' 'Points 2: Class III Moderate (6.6% complications)\n' 'Points 3 or more : Class IV High (>11% complications)') rcri_class = fields.Selection([ (None, ''), ('I', 'I'), ('II', 'II'), ('III', 'III'), ('IV', 'IV'), ], 'RCRI Class', sort=False) @fields.depends('rcri_high_risk_surgery', 'rcri_ischemic_history', 'rcri_congestive_history', 'rcri_diabetes_history', 'rcri_cerebrovascular_history', 'rcri_kidney_history') def on_change_with_rcri_total(self): total = 0 if self.rcri_high_risk_surgery: total = total + 1 if self.rcri_ischemic_history: total = total + 1 if self.rcri_congestive_history: total = total + 1 if self.rcri_diabetes_history: total = total + 1 if self.rcri_kidney_history: total = total + 1 if self.rcri_cerebrovascular_history: total = total + 1 return total @fields.depends('rcri_high_risk_surgery', 'rcri_ischemic_history', 'rcri_congestive_history', 'rcri_diabetes_history', 'rcri_cerebrovascular_history', 'rcri_kidney_history') def on_change_with_rcri_class(self): rcri_class = '' total = 0 if self.rcri_high_risk_surgery: total = total + 1 if self.rcri_ischemic_history: total = total + 1 if self.rcri_congestive_history: total = total + 1 if self.rcri_diabetes_history: total = total + 1 if self.rcri_kidney_history: total = total + 1 if self.rcri_cerebrovascular_history: total = total + 1 if total == 0: rcri_class = 'I' if total == 1: rcri_class = 'II' if total == 2: rcri_class = 'III' if (total > 2): rcri_class = 'IV' return rcri_class @staticmethod def default_rcri_date(): return datetime.now() @staticmethod def default_rcri_total(): return 0 @staticmethod def default_rcri_class(): return 'I' def get_rec_name(self, name): res = 'Points: ' + str(self.rcri_total) + ' (Class ' + \ str(self.rcri_class) + ')' return res
class PriceData(ModelSQL, ModelView): """Pricedata""" __name__ = "price_master_datas.pricedata" product_name = fields.Char('product_name', select=True) # 产品名称 product_code = fields.Char('product_code', select=True) # 药品编码 retrieve_the_code = fields.Many2One('product.product', 'retrieve_the_code', required=True, select=True) code = fields.Function( fields.Char("Code", size=None, select=True, states=STATES, depends=DEPENDS), 'get_code', 'set_code') name = fields.Function( fields.Char("Name", size=None, required=False, translate=True, select=True), "get_name", "set_name") attach = fields.Char('attach', select=True) drug_specifications = fields.Char('drug_specifications', select=True, required=False) cost_price = fields.Numeric('cost_price', select=True, digits=price_digits) list_price = fields.Numeric('list_price', select=True, digits=price_digits) new_cost_price = fields.Float('new_cost_price', select=True, required=True, digits=price_digits) new_list_price = fields.Float('new_list_price', select=True, required=True, digits=price_digits) modify_reason = fields.Selection([ ('00', u'来药单'), ('01', u'海虹药通'), ], 'Modify_reason', select=True) effective_date = fields.Date('effective_date', select=True, required=True) price_digits = (16, config.getint('product', '', default=4)) # 小数点保留问题 @classmethod def delete(cls, records): return cls.raise_user_error(u'历史单据不允许删除') # 错误信息弹框提示 @classmethod def write(cls, records, values, *args): return cls.raise_user_error(u'历史单据不允许修改') @classmethod def create(cls, vlist): UserId = Pool().get('hrp_internal_delivery.test_straight') location_id = UserId.get_warehouse_frozen_id() # Config = Pool().get('purchase.configuration') # config = Config(1) # 库存地配置 # location_id = [config.hospital.id,config.outpatient_service.id,config.warehouse.id,config.medical.id,config.endoscopic.id,config.preparation.id,config.ward.id,config.herbs.id] price_content = Pool().get('hrp_report.price_profit_loss_content') UomCategory = Pool().get('product.category') Date = Pool().get('ir.date') today = str(Date.today()) price = Pool().get("product.template") product = Pool().get("product.product") if str(vlist[0]['effective_date']) == today: product_id = vlist[0]['retrieve_the_code'] Product = product.search([('id', '=', product_id)]) product_template_id = Product[0].template.id New = price.search([('id', '=', product_template_id)]) if New: lvc = { 'list_price': vlist[0]['new_list_price'], 'cost_price': vlist[0]['new_cost_price'] } price.write(New, lvc) content = [] for each in location_id: content_dict = {} with Transaction().set_context( stock_date_end=Date.today()): quantities = product.products_by_location( [each['warehouse']], [product_id], with_childs=True) if quantities.values(): stock_level_warehouse = [ v for v in quantities.values() ][0] else: stock_level_warehouse = 0 with Transaction().set_context( stock_date_end=Date.today()): quantities = product.products_by_location( [each['freeze']], [product_id], with_childs=True) if quantities.values(): stock_level_freeze = [ v for v in quantities.values() ][0] else: stock_level_freeze = 0 stock_level = stock_level_warehouse + stock_level_freeze party = Product[0].product_suppliers if party: party = party[0].party.name else: party = '' categories = [i.id for i in Product[0].categories] uom_category = UomCategory.search([('id', '=', categories[0])]) uom_name = uom_category[0].name if uom_name == u'西药': content_dict['drug_type'] = '00' if uom_name == u'中成药': content_dict['drug_type'] = '01' if uom_name == u'中草药': content_dict['drug_type'] = '02' if uom_name == u'颗粒中': content_dict['drug_type'] = '03' if uom_name == u'原料药': content_dict['drug_type'] = '04' if uom_name == u'敷药': content_dict['drug_type'] = '05' if uom_name == u'同位素': content_dict['drug_type'] = '07' content_dict['location'] = each['warehouse'] content_dict['code'] = vlist[0]['product_code'] content_dict['product'] = vlist[0]['product_name'] content_dict['drug_specifications'] = vlist[0][ 'drug_specifications'] content_dict['list_price'] = vlist[0]['list_price'] content_dict['cost_price'] = vlist[0]['cost_price'] content_dict['new_list_price'] = vlist[0][ 'new_list_price'] content_dict['new_cost_price'] = vlist[0][ 'new_cost_price'] content_dict['inventory'] = float(stock_level) content_dict['effective_date'] = today content_dict['party'] = party content_dict['uom'] = Product[ 0].template.default_uom.id content_dict['price_profit_loss'] = decimal.Decimal( str(stock_level)) * (decimal.Decimal( str(vlist[0]['new_cost_price'])) - vlist[0]['cost_price']) content_dict[ 'price_list_profit_loss'] = decimal.Decimal( str(stock_level)) * (decimal.Decimal( str(vlist[0]['new_list_price'])) - vlist[0]['list_price']) content.append(content_dict) price_content.create(content) else: pass return super(PriceData, cls).create(vlist) @classmethod def create_profit_loss(cls, Pricedata): for price_data in Pricedata: UserId = Pool().get('hrp_internal_delivery.test_straight') location_id = UserId.get_warehouse_frozen_id() price_content = Pool().get('hrp_report.price_profit_loss_content') UomCategory = Pool().get('product.category') Date = Pool().get('ir.date') today = str(Date.today()) product = Pool().get('product.product') content = [] for each in location_id: dict = {} with Transaction().set_context(stock_date_end=Date.today()): quantities = product.products_by_location( [each['warehouse']], [price_data.retrieve_the_code.id], with_childs=True) if quantities.values(): stock_level_warehouse = [ v for v in quantities.values() ][0] else: stock_level_warehouse = 0 with Transaction().set_context(stock_date_end=Date.today()): quantities = product.products_by_location( [each['freeze']], [price_data.retrieve_the_code.id], with_childs=True) if quantities.values(): stock_level_freeze = [v for v in quantities.values()][0] else: stock_level_freeze = 0 stock_level = stock_level_warehouse + stock_level_freeze party = price_data.retrieve_the_code.product_suppliers if party: party = party[0].party.name else: party = '' categories = [ i.id for i in price_data.retrieve_the_code.categories ] uom_category = UomCategory.search([('id', '=', categories[0])]) uom_name = uom_category[0].name if uom_name == u'西药': dict['drug_type'] = '00' if uom_name == u'中成药': dict['drug_type'] = '01' if uom_name == u'中草药': dict['drug_type'] = '02' if uom_name == u'颗粒中': dict['drug_type'] = '03' if uom_name == u'原料药': dict['drug_type'] = '04' if uom_name == u'敷药': dict['drug_type'] = '05' if uom_name == u'同位素': dict['drug_type'] = '07' dict['location'] = each['warehouse'] dict['code'] = price_data.product_code dict['product'] = price_data.product_name dict[ 'drug_specifications'] = price_data.drug_specifications dict['list_price'] = price_data.list_price dict['cost_price'] = price_data.cost_price dict['new_list_price'] = price_data.new_list_price dict['new_cost_price'] = price_data.new_cost_price dict['inventory'] = float(stock_level) dict['effective_date'] = today dict['party'] = party dict[ 'uom'] = price_data.retrieve_the_code.template.default_uom.id dict['price_profit_loss'] = decimal.Decimal( str(stock_level)) * ( decimal.Decimal(str(price_data.new_cost_price)) - price_data.cost_price) dict['price_list_profit_loss'] = decimal.Decimal( str(stock_level)) * ( decimal.Decimal(str(price_data.new_list_price)) - price_data.list_price) content.append(dict) price_content.create(content) @fields.depends('retrieve_the_code') def on_change_retrieve_the_code(self): if self.retrieve_the_code != '': try: Retrieve = self.retrieve_the_code.name Attach = self.retrieve_the_code.attach Code = self.retrieve_the_code.code Drug_specifications = self.retrieve_the_code.drug_specifications Cost_price = self.retrieve_the_code.cost_price List_price = self.retrieve_the_code.list_price hrp = Pool().get('product.product') HRP = hrp.search([('id', '=', self.retrieve_the_code.id)]) if HRP: try: self.product_name = Retrieve self.product_code = Code self.name = Retrieve self.attach = Attach self.code = Code self.drug_specifications = str(Drug_specifications) self.cost_price = Cost_price self.list_price = List_price except: pass except: pass def get_code(self, name): pool = Pool() try: product_templates = pool.get('product.product') product_template = product_templates.search([ ("id", "=", int(self.retrieve_the_code.mark.id)) ]) code = product_template[0].code except: return None return code def get_name(self, name): pool = Pool() try: product_templates = pool.get('product.template') product_template = product_templates.search([ ("id", "=", int(self.retrieve_the_code.mark.id)) ]) name = product_template[0].name except: return None return name @classmethod def set_code(cls, set_code, name, value): pass @classmethod def set_name(cls, set_name, name, value): pass @classmethod def write_price(cls): Price = Pool().get('price_master_datas.pricedata') with Transaction().set_context(user=1): Date = Pool().get('ir.date') today = str(Date.today()) pricedata = Pool().get('price_master_datas.pricedata') Pricedata = pricedata.search([('effective_date', '=', today)]) price = Pool().get("product.template") if Pricedata: for i in Pricedata: Retri = i.retrieve_the_code.template.id New_cost_price = i.new_cost_price New_list_price = i.new_list_price New = price.search([('id', '=', Retri)]) if New: lvc = { 'list_price': New_list_price, 'cost_price': New_cost_price } price.write(New, lvc) else: pass Price.create_profit_loss(Pricedata)
class RenumerationPurposeandPay(ModelSQL, ModelView): '''Renumeration Purpose and Pay''' __name__ = 'exam_type.purpose_and_pay_renum' renumeration = fields.Many2One('exam_section.renumeration_bill', 'Renumeration Bill') exam_type = fields.Function( fields.Many2One('exam_section.exam_type', 'Exam Type'), 'on_change_with_exam_type') external = fields.Function(fields.Boolean('External'), 'on_change_with_external') payment_basis = fields.Char('Payment Basis') unit = fields.Integer('Unit') purpose = fields.Many2One('exam_section.exam_type.renumeration', 'Purpose', domain=[('exam_type', '=', Eval('exam_type')), ('is_external', '=', Eval('external'))], depends=['exam_type', 'external']) amount = fields.Function(fields.Float('Amount'), 'calculate_amount_payable') @classmethod def __setup__(cls): super().__setup__() cls._error_messages.update({ 'amt_lt_0': 'Amount is less than zero', 'max_range': 'Maximum Amount exceeded', }) @classmethod def validate(cls, records): super(RenumerationPurposeandPay, cls).validate(records) for record in records: record.check_amount() def check_amount(self): '''Check whether: (i) amount is less thanm zero or not (ii) amount exceeds maximum amount allowed for spefific purpose or not''' amount = self.amount purpose = self.purpose if amount and purpose: if amount < 0: self.raise_user_error('amt_lt_0') elif purpose.max_range != 0 and amount > purpose.max_range: self.raise_user_error('max_range') # @classmethod # def show_exam_type(cls, records): # for rec in records: # print(rec.renumeration.exam.exam_type) @fields.depends('renumeration') def on_change_with_external(self, name=None): '''Function for hidden field to determine whether bill is for AIIMS Employee or external examiner''' if self.renumeration: if self.renumeration.type_of_examiner == 'external': return True return False @fields.depends('renumeration') def on_change_with_exam_type(self, name=None): '''Function for hidden field to fetch type of examination''' if self.renumeration and self.renumeration.exam: return self.renumeration.exam.exam_type.id @fields.depends('purpose') def on_change_with_payment_basis(self): '''Function for fetching payment basis (Daily, Hourly, Per Session, Per Copy, Per Exam)''' if self.purpose and self.purpose.payment_basis: return self.purpose.payment_basis.name @fields.depends('purpose', 'renumeration') def calculate_amount_payable(self, name): '''Calculate total amount for Renumeration Bill''' res = 0 if self.purpose and self.unit: res = self.unit * self.purpose.type_amount_fix if self.purpose.min_range and res < self.purpose.min_range: res = self.purpose.min_range return res
class FiscalYear(Workflow, ModelSQL, ModelView): 'Fiscal Year' __name__ = 'account.fiscalyear' name = fields.Char('Name', size=None, required=True, depends=DEPENDS) start_date = fields.Date('Starting Date', required=True, states=STATES, domain=[('start_date', '<=', Eval('end_date', None))], depends=DEPENDS + ['end_date']) end_date = fields.Date('Ending Date', required=True, states=STATES, domain=[('end_date', '>=', Eval('start_date', None))], depends=DEPENDS + ['start_date']) periods = fields.One2Many('account.period', 'fiscalyear', 'Periods', states=STATES, depends=DEPENDS) state = fields.Selection([ ('open', 'Open'), ('close', 'Close'), ('locked', 'Locked'), ], 'State', readonly=True, required=True) post_move_sequence = fields.Many2One( 'ir.sequence', 'Post Move Sequence', required=True, domain=[ ('code', '=', 'account.move'), ['OR', ('company', '=', Eval('company')), ('company', '=', None)] ], context={ 'code': 'account.move', 'company': Eval('company'), }, depends=['company']) company = fields.Many2One( 'company.company', 'Company', required=True, domain=[ ('id', If(Eval('context', {}).contains('company'), '=', '!='), Eval('context', {}).get('company', -1)), ], select=True) icon = fields.Function(fields.Char("Icon"), 'get_icon') @classmethod def __setup__(cls): super(FiscalYear, cls).__setup__() cls._order.insert(0, ('start_date', 'ASC')) cls._error_messages.update({ 'change_post_move_sequence': ('You can not change the post ' 'move sequence in fiscal year "%s".'), 'no_fiscalyear_date': 'No fiscal year defined for "%s".', 'fiscalyear_overlaps': ('Fiscal year "%(first)s" and ' '"%(second)s" overlap.'), 'different_post_move_sequence': ('Fiscal year "%(first)s" and ' '"%(second)s" have the same post move sequence.'), 'account_balance_not_zero': ('The balance of the account "%s" ' 'must be zero.'), 'close_error': ('You can not close fiscal year "%s" until you ' 'close all previous fiscal years.'), 'reopen_error': ('You can not reopen fiscal year "%s" until ' 'you reopen all later fiscal years.'), }) cls._transitions |= set(( ('open', 'close'), ('close', 'locked'), ('close', 'open'), )) cls._buttons.update({ 'create_period': { 'invisible': ((Eval('state') != 'open') | Eval('periods', [0])), 'depends': ['state'], }, 'create_period_3': { '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'], }, }) @staticmethod def default_state(): return 'open' @staticmethod def default_company(): return Transaction().context.get('company') def get_icon(self, name): return { 'open': 'tryton-open', 'close': 'tryton-close', 'locked': 'tryton-readonly', }.get(self.state) @classmethod def validate(cls, years): super(FiscalYear, cls).validate(years) for year in years: year.check_dates() year.check_post_move_sequence() def check_dates(self): transaction = Transaction() connection = transaction.connection transaction.database.lock(connection, self._table) cursor = connection.cursor() table = self.__table__() cursor.execute( *table.select(table.id, where=(((table.start_date <= self.start_date) & (table.end_date >= self.start_date)) | ((table.start_date <= self.end_date) & (table.end_date >= self.end_date)) | ((table.start_date >= self.start_date) & (table.end_date <= self.end_date))) & (table.company == self.company.id) & (table.id != self.id))) second_id = cursor.fetchone() if second_id: second = self.__class__(second_id[0]) self.raise_user_error('fiscalyear_overlaps', { 'first': self.rec_name, 'second': second.rec_name, }) def check_post_move_sequence(self): years = self.search([ ('post_move_sequence', '=', self.post_move_sequence.id), ('id', '!=', self.id), ]) if years: self.raise_user_error('different_post_move_sequence', { 'first': self.rec_name, 'second': years[0].rec_name, }) @classmethod def write(cls, *args): 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']): cls.raise_user_error('change_post_move_sequence', (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 @ModelView.button def create_period(cls, fiscalyears, interval=1): ''' 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: period_end_date = period_start_date + \ relativedelta(months=interval - 1) + \ relativedelta(day=31) if period_end_date > fiscalyear.end_date: period_end_date = fiscalyear.end_date name = datetime_strftime(period_start_date, '%Y-%m') if name != datetime_strftime(period_end_date, '%Y-%m'): name += ' - ' + datetime_strftime(period_end_date, '%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 def create_period_3(cls, fiscalyears): ''' Create periods for the fiscal years with 3 months interval ''' cls.create_period(fiscalyears, interval=3) @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: 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() cls.raise_user_error('no_fiscalyear_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 account.kind == 'view': return if not account.deferral: if not Currency.is_zero(self.company.currency, account.balance): self.raise_user_error('account_balance_not_zero', error_args=(account.rec_name, )) else: deferral = Deferral() deferral.account = account deferral.fiscalyear = self deferral.debit = account.debit deferral.credit = account.credit 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') transaction = Transaction() database = transaction.database connection = transaction.connection # Lock period to be sure no new period will be created in between. database.lock(connection, Period._table) deferrals = [] for fiscalyear in fiscalyears: if cls.search([ ('end_date', '<=', fiscalyear.start_date), ('state', '=', 'open'), ('company', '=', fiscalyear.company.id), ]): cls.raise_user_error('close_error', (fiscalyear.rec_name, )) periods = Period.search([ ('fiscalyear', '=', fiscalyear.id), ]) Period.close(periods) with Transaction().set_context(fiscalyear=fiscalyear.id, date=None, cumulate=True): accounts = Account.search([ ('company', '=', fiscalyear.company.id), ]) deferrals += [ _f for _f in (fiscalyear.get_deferral(a) for a in accounts) if _f ] 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), ]): cls.raise_user_error('reopen_error') 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)
class TADAHotelFood(ModelSQL, ModelView): '''TA/DA Hotel/Food''' __name__ = 'exam_section.ta_da.hotel_food' ta_da = fields.Many2One('exam_section.ta_da_bill', 'TA/DA Bill') type_ = fields.Selection([('hotel', 'Hotel'), ('food', 'Food')], 'Type', required=True) no_of_nights_stayed = fields.Integer('No. of Nights Stayed', states={ 'readonly': ~Eval('type_').in_(['hotel']), 'invisible': ~Eval('type_').in_(['hotel']), }, depends=['type_']) no_of_days_food = fields.Integer('No. of Days Food', states={ 'readonly': ~Eval('type_').in_(['food']), 'invisible': ~Eval('type_').in_(['food']), }, depends=['type_']) from_date = fields.Date('From Date', required=True) to_date = fields.Date('To Date', required=True) amount = fields.Float('Amount', required=True) have_bill = fields.Boolean('Bill?') bill = fields.Many2One('ir.attachment', 'Bill', states={ 'invisible': Eval('have_bill') != '1', 'required': Eval('have_bill') == '1', }, depends=['have_bill']) @classmethod def __setup__(cls): super().__setup__() cls._error_messages.update({ 'date_error': 'From date is more than to date', 'amount_lt_0': 'Amount cannot be less than 0' }) @staticmethod def default_amount(): return 0 @fields.depends('from_date', 'to_date', 'type_') def on_change_with_no_of_nights_stayed(self): '''Calculate number of nights stayed in hotel using from_date and to_date''' if self.type_ == 'hotel': if self.from_date: if self.to_date: no_of_days_delta = self.to_date - self.from_date return int(no_of_days_delta.days) return 0 @fields.depends('from_date', 'to_date', 'type_') def on_change_with_no_of_days_food(self): '''Calculate number of days of food consumption using from_date and to_date''' if self.type_ == 'food': if self.from_date: if self.to_date: no_of_days_delta = self.to_date - self.from_date return int(no_of_days_delta.days) return 0 @classmethod def validate(cls, records): super(TADAHotelFood, cls).validate(records) for record in records: record.check_date() record.check_amount() def check_date(self): '''Check whether from_date is greater than to_date or not''' if self.from_date > self.to_date: self.raise_user_error('date_error') def check_amount(self): '''Check whether amount is less than zero or not''' if self.amount < 0: self.raise_user_error('amount_lt_0')
class ModelViewChangedValuesTarget(ModelView): 'ModelView Changed Values Target' __name__ = 'test.modelview.changed_values.target' name = fields.Char('Name') parent = fields.Many2One('test.modelview.changed_values', 'Parent')
class OtEmployee(metaclass=PoolMeta): __name__ = 'company.employee' employee_list = fields.Many2One('ota.list', 'Employee List')
class Line(ModelSQL, ModelView): 'Analytic Line' __name__ = 'analytic_account.line' debit = fields.Numeric('Debit', digits=(16, Eval('currency_digits', 2)), required=True, depends=['currency_digits']) credit = fields.Numeric('Credit', digits=(16, Eval('currency_digits', 2)), required=True, depends=['currency_digits']) currency_digits = fields.Function(fields.Integer('Currency Digits'), 'on_change_with_currency_digits') company = fields.Function(fields.Many2One('company.company', 'Company'), 'on_change_with_company', searcher='search_company') account = fields.Many2One('analytic_account.account', 'Account', required=True, select=True, domain=[ ('type', 'not in', ['view', 'distribution']), [ 'OR', ('company', '=', None), ('company', '=', Eval('company', -1)), ], ], depends=['company']) move_line = fields.Many2One('account.move.line', 'Account Move Line', ondelete='CASCADE', required=True) date = fields.Date('Date', required=True) @classmethod def __setup__(cls): super(Line, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('credit_debit_', Check(t, t.credit * t.debit == 0), 'account.msg_line_debit_credit'), ] cls._order.insert(0, ('date', 'ASC')) @classmethod def __register__(cls, module_name): super(Line, cls).__register__(module_name) table = cls.__table_handler__(module_name) # Migration from 4.0: remove name and journal for field_name in ['name', 'journal']: table.not_null_action(field_name, action='remove') # Migration from 5.0: replace credit_debit constraint by credit_debit_ table.drop_constraint('credit_debit') @staticmethod def default_date(): Date = Pool().get('ir.date') return Date.today() @staticmethod def default_debit(): return Decimal(0) @staticmethod def default_credit(): return Decimal(0) @fields.depends('move_line', '_parent_move_line.account') def on_change_with_currency_digits(self, name=None): if self.move_line and self.move_line.account: return self.move_line.account.company.currency.digits return 2 @fields.depends('move_line', '_parent_move_line.account') def on_change_with_company(self, name=None): if self.move_line and self.move_line.account: return self.move_line.account.company.id @classmethod def search_company(cls, name, clause): return [('move_line.account.' + clause[0], ) + tuple(clause[1:])] @fields.depends('move_line', '_parent_move_line.date', '_parent_move_line.debit', '_parent_move_line.credit') def on_change_move_line(self): if self.move_line: self.date = self.move_line.date self.debit = self.move_line.debit self.credit = self.move_line.credit @staticmethod def query_get(table): ''' Return SQL clause for analytic line depending of the context. table is the SQL instance of the analytic_account_line table. ''' clause = Literal(True) if Transaction().context.get('start_date'): clause &= table.date >= Transaction().context['start_date'] if Transaction().context.get('end_date'): clause &= table.date <= Transaction().context['end_date'] return clause @classmethod def create(cls, vlist): pool = Pool() MoveLine = pool.get('account.move.line') lines = super(Line, cls).create(vlist) move_lines = [l.move_line for l in lines] MoveLine.set_analytic_state(move_lines) MoveLine.save(move_lines) return lines @classmethod def write(cls, *args): pool = Pool() MoveLine = pool.get('account.move.line') super(Line, cls).write(*args) lines = sum(args[0:None:2], []) move_lines = [l.move_line for l in lines] MoveLine.set_analytic_state(move_lines) MoveLine.save(move_lines) @classmethod def delete(cls, lines): pool = Pool() MoveLine = pool.get('account.move.line') move_lines = [l.move_line for l in lines] super(Line, cls).delete(lines) MoveLine.set_analytic_state(move_lines) MoveLine.save(move_lines)
@classmethod def view_attributes(cls): storage_types = Eval('type').in_(['storage', 'warehouse', 'view']) return super().view_attributes() + [ ('/tree/field[@name="quantity"]', 'visual', If(storage_types & (Eval('quantity', 0) < 0), 'danger', ''), ['type']), ('/tree/field[@name="forecast_quantity"]', 'visual', If(storage_types & (Eval('forecast_quantity', 0) < 0), 'warning', ''), ['type']), ] supplier_location = fields.Many2One( 'stock.location', "Supplier Location", domain=[('type', '=', 'supplier')], help="The default source location for stock received from the party.") customer_location = fields.Many2One( 'stock.location', "Customer Location", domain=[('type', '=', 'customer')], help="The default destination location for stock sent to the party.") class Party(metaclass=PoolMeta): __name__ = 'party.party' supplier_location = fields.MultiValue(supplier_location) customer_location = fields.MultiValue(customer_location) locations = fields.One2Many('party.party.location', 'party', "Locations")
class HouseRentAllowance(Workflow, ModelSQL, ModelView): """House Rent Allowance for an Employee""" __name__ = 'hr.allowance.hra' salary_code = fields.Char( 'Salary Code', required=True, states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) employee = fields.Many2One( 'company.employee', 'Employee', required=True, states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) designation = fields.Many2One('employee.designation', 'Designation', required=True) department = fields.Many2One( 'company.department', 'Department', required=True, ) from_date = fields.Date('From Date', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) to_date = fields.Date('To Date', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) address = fields.Char('House Address', states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) amount = fields.Float('Expenditure On Rent', required=True, states={'readonly': ~Eval('state').in_(['draft'])}, depends=['state']) state = fields.Selection([ ('draft', 'Draft'), ('confirm', 'Confirm'), ('submit', 'Submit'), ('cash_section_officer', 'Cash Section_officer'), ('cancel', 'Cancel'), ('approve', 'Approve'), ], 'Status', readonly=True) @staticmethod def default_from_date(): return datetime.date.today() @classmethod def __setup__(cls): super().__setup__() cls._buttons.update({ "submit": { 'invisible': ~Eval('state').in_(['draft', 'confirm']), }, "cancel": { 'invisible': ~Eval('state').in_(['cash_section_officer']), }, "approve": { 'invisible': ~Eval('state').in_(['cash_section_officer']), }, }) cls._transitions |= set(( ('draft', 'confirm'), ('confirm', 'submit'), ('submit', 'cash_section_officer'), ('cash_section_officer', 'approve'), ('cash_section_officer', 'cancel'), )) @staticmethod def default_state(): return 'draft' @classmethod @ModelView.button @ModelView.button_action('submit') def submit(cls, records): pass @classmethod @ModelView.button @Workflow.transition('confirm') def confirm(cls, records): pass @classmethod @ModelView.button @Workflow.transition('cash_section_officer') def submit(cls, records): pass @classmethod @ModelView.button @Workflow.transition('approve') def approve(cls, records): pass @classmethod @ModelView.button @Workflow.transition('cancel') def cancel(cls, records): pass @fields.depends('employee') def on_change_employee(self): if self.employee: self.salary_code = self.employee.salary_code if self.employee.salary_code else None self.designation = self.employee.designation if self.employee.designation else None self.department = self.employee.department if self.employee.department else None @staticmethod def default_employee(): global current_employee current_employee = None pool = Pool() Employee = pool.get('company.employee') employee_id = Transaction().context.get('employee') employee = Employee.search([('id', '=', employee_id)]) if employee != []: current_employee = employee[0] return current_employee.id if current_employee else None
class ProductsByLocations(DeactivableMixin, ModelSQL, ModelView): "Products by Locations" __name__ = 'stock.products_by_locations' product = fields.Many2One('product.product', "Product") quantity = fields.Function(fields.Float( "Quantity", digits=(16, Eval('default_uom_digits', 2))), 'get_product', searcher='search_product') forecast_quantity = fields.Function(fields.Float( "Forecast Quantity", digits=(16, Eval('default_uom_digits', 2))), 'get_product', searcher='search_product') default_uom = fields.Function(fields.Many2One('product.uom', "Default UOM"), 'get_product', searcher='search_product') default_uom_digits = fields.Function(fields.Integer("Default UOM Digits"), 'get_product') cost_value = fields.Function(fields.Numeric("Cost Value"), 'get_product') consumable = fields.Function(fields.Boolean("Consumable"), 'get_product', searcher='search_product') @classmethod def __setup__(cls): super().__setup__() cls._order.insert(0, ('product', 'ASC')) @classmethod def table_query(cls): pool = Pool() Product = pool.get('product.product') product = Product.__table__() columns = [] for fname, field in cls._fields.items(): if not hasattr(field, 'set'): if (isinstance(field, fields.Many2One) and field.get_target() == Product): column = Column(product, 'id') else: column = Column(product, fname) columns.append(column.as_(fname)) return product.select(*columns) 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:])] def get_product(self, name): value = getattr(self.product, name) if isinstance(value, Model): value = value.id return value @classmethod def search_product(cls, name, clause): nested = clause[0].lstrip(name) return [('product.' + name + nested, ) + tuple(clause[1:])]
class Sale: "Sale" __name__ = 'sale.sale' is_international_shipping = fields.Function( fields.Boolean("Is International Shipping"), 'on_change_with_is_international_shipping' ) package_weight = fields.Function( fields.Float( "Package weight", digits=(16, Eval('weight_digits', 2)), depends=['weight_digits'], ), 'get_package_weight' ) total_weight = fields.Function( fields.Float( "Total weight", digits=(16, Eval('weight_digits', 2)), depends=['weight_digits'], ), 'get_total_weight' ) weight_uom = fields.Function( fields.Many2One('product.uom', 'Weight UOM'), 'get_weight_uom' ) weight_digits = fields.Function( fields.Integer('Weight Digits'), 'on_change_with_weight_digits' ) @classmethod def __setup__(cls): super(Sale, cls).__setup__() cls._error_messages.update({ 'warehouse_address_missing': 'Warehouse address is missing', }) @fields.depends('weight_uom') def on_change_with_weight_digits(self, name=None): if self.weight_uom: return self.weight_uom.digits return 2 def get_weight_uom(self, name): """ Returns weight uom for the package """ return self._get_weight_uom().id def _get_weight_uom(self): """ Returns Pound as default value for uom Downstream module can override this method to change weight uom as per carrier """ UOM = Pool().get('product.uom') return UOM.search([('symbol', '=', 'lb')])[0] def get_package_weight(self, name): """ Returns sum of weight associated with each line """ warnings.warn( 'Field package_weight is depricated, use total_weight instead', DeprecationWarning, stacklevel=2 ) weight_uom = self._get_weight_uom() return self._get_package_weight(weight_uom) def get_total_weight(self, name): """ Returns sum of weight associated with each line """ weight_uom = self._get_weight_uom() return self._get_total_weight(weight_uom) @fields.depends('party', 'shipment_address', 'warehouse') def on_change_with_is_international_shipping(self, name=None): """ Return True if international shipping """ from_address = self._get_ship_from_address() if self.shipment_address and from_address and \ from_address.country and self.shipment_address.country and \ from_address.country != self.shipment_address.country: return True return False def _get_package_weight(self, uom): """ Returns sum of weight associated with package """ warnings.warn( '_get_package_weight is depricated, use _get_total_weight instead', DeprecationWarning, stacklevel=2 ) return sum( map( lambda line: line.get_weight(uom, silent=True), self.lines ) ) def _get_total_weight(self, uom): """ Returns sum of weight for given uom """ return sum( map( lambda line: line.get_weight(uom, silent=True), self.lines ) ) def _get_ship_from_address(self): """ Usually the warehouse from which you ship """ if not self.warehouse.address: return self.raise_user_error('warehouse_address_missing') return self.warehouse and self.warehouse.address def add_shipping_line(self, shipment_cost, description): """ This method takes shipping_cost and description as arguments and writes a shipping line. It deletes any previous shipping lines which have a shipment_cost. :param shipment_cost: The shipment cost calculated according to carrier :param description: Shipping line description """ self.__class__.write([self], { 'lines': [ ('create', [{ 'type': 'line', 'product': self.carrier.carrier_product.id, 'description': description, 'quantity': 1, # XXX 'unit': self.carrier.carrier_product.sale_uom.id, 'unit_price': shipment_cost, 'shipment_cost': shipment_cost, 'amount': shipment_cost, 'taxes': [], 'sequence': 9999, # XXX }]), ('delete', [ line for line in self.lines if line.shipment_cost is not None ]), ] }) def _get_carrier_context(self): "Pass sale in the context" context = super(Sale, self)._get_carrier_context() context = context.copy() context['sale'] = self.id return context def apply_product_shipping(self): """ This method apply product(carrier) shipping. """ Currency = Pool().get('currency.currency') with Transaction().set_context(self._get_carrier_context()): shipment_cost, currency_id = self.carrier.get_sale_price() shipment_cost = Currency.compute( Currency(currency_id), shipment_cost, self.currency ) self.add_shipping_line(shipment_cost, self.carrier.rec_name) def get_shipping_rates(self, carrier): """ Return list of tuples as: [ ( <display method name>, <cost>, <currency>, <metadata>, <write_vals> ) ... ] """ Currency = Pool().get('currency.currency') if carrier.carrier_cost_method == 'product': with Transaction().set_context(self._get_carrier_context()): cost, currency_id = carrier.get_sale_price() return [( carrier.rec_name, cost, Currency(currency_id), {}, {'carrier': carrier.id}, )] return [] @classmethod def get_allowed_carriers_domain(cls): """This method returns domain to seach allowed carriers Downstream modules can inherit and update customize this domain. """ return [] def get_all_shipping_rates(self): """ Return shipping rates for all allowed carriers Return list of tuples as: [ ( <display method name>, <rate>, <currency>, <metadata>, <write_vals> ) ... ] """ Carrier = Pool().get('carrier') carriers = Carrier.search(self.get_allowed_carriers_domain()) errors = [] rate_list = [] for carrier in carriers: try: rate_list += self.get_shipping_rates(carrier=carrier) except UserError, e: # XXX: Collect all errors from shipping carriers errors.append(e.message) if not rate_list and errors: raise self.raise_user_error('\n'.join(errors)) return rate_list
class Identifier(sequence_ordered(), ModelSQL, ModelView): 'Party Identifier' __name__ = 'party.identifier' _rec_name = 'code' party = fields.Many2One('party.party', 'Party', ondelete='CASCADE', required=True, select=True, help="The party identified by this record.") type = fields.Selection('get_types', 'Type') type_string = type.translated('type') code = fields.Char('Code', required=True) @classmethod def __register__(cls, module_name): pool = Pool() Party = pool.get('party.party') cursor = Transaction().connection.cursor() party = Party.__table__() table = cls.__table__() super().__register__(module_name) party_h = Party.__table_handler__(module_name) if (party_h.column_exist('vat_number') and party_h.column_exist('vat_country')): identifiers = [] cursor.execute(*party.select( party.id, party.vat_number, party.vat_country, where=(party.vat_number != Null) | (party.vat_country != Null))) for party_id, number, country in cursor: code = (country or '') + (number or '') if not code: continue for type in Party.tax_identifier_types(): module = get_cc_module(*type.split('_', 1)) if module.is_valid(code): break else: type = None identifiers.append( cls(party=party_id, code=code, type=type)) cls.save(identifiers) party_h.drop_column('vat_number') party_h.drop_column('vat_country') # Migration from 5.8: Rename cn_rit into cn_ric cursor.execute(*table.update([table.type], ['cn_ric'], where=(table.type == 'cn_rit'))) @classmethod def get_types(cls): pool = Pool() Configuration = pool.get('party.configuration') configuration = Configuration(1) return [(None, '')] + configuration.get_identifier_types() @fields.depends('type', 'code') def on_change_with_code(self): if self.type and '_' in self.type: module = get_cc_module(*self.type.split('_', 1)) if module: try: return module.compact(self.code) except stdnum.exceptions.ValidationError: pass return self.code def pre_validate(self): super().pre_validate() self.check_code() @fields.depends('type', 'party', 'code') def check_code(self): if self.type and '_' in self.type: module = get_cc_module(*self.type.split('_', 1)) if module: if not module.is_valid(self.code): if self.party and self.party.id > 0: party = self.party.rec_name else: party = '' raise InvalidIdentifierCode( gettext('party.msg_invalid_code', type=self.type_string, code=self.code, party=party))
class Line(sequence_ordered(), ModelSQL, ModelView): "Subscription Line" __name__ = 'sale.subscription.line' _rec_name = 'description' subscription = fields.Many2One( 'sale.subscription', "Subscription", required=True, select=True, ondelete='CASCADE', states={ 'readonly': ((Eval('subscription_state') != 'draft') & Bool(Eval('subscription'))), }, depends=['subscription_state'], help="Add the line below the subscription.") subscription_state = fields.Function( fields.Selection(STATES, "Subscription State"), 'on_change_with_subscription_state') subscription_start_date = fields.Function( fields.Date("Subscription Start Date"), 'on_change_with_subscription_start_date') subscription_end_date = fields.Function( fields.Date("Subscription End Date"), 'on_change_with_subscription_end_date') service = fields.Many2One( 'sale.subscription.service', "Service", required=True, states={ 'readonly': Eval('subscription_state') != 'draft', }, depends=['subscription_state']) description = fields.Text("Description", required=True, states={ 'readonly': Eval('subscription_state') != 'draft', }, depends=['subscription_state']) quantity = fields.Float( "Quantity", digits=(16, Eval('unit_digits', 2)), states={ 'readonly': Eval('subscription_state') != 'draft', 'required': Bool(Eval('consumption_recurrence')), }, depends=[ 'unit_digits', 'subscription_state', 'consumption_recurrence']) unit = fields.Many2One( 'product.uom', "Unit", required=True, states={ 'readonly': Eval('subscription_state') != 'draft', }, domain=[ If(Bool(Eval('service_unit_category')), ('category', '=', Eval('service_unit_category')), ('category', '!=', -1)), ], depends=['subscription_state', 'service_unit_category']) unit_digits = fields.Function( fields.Integer("Unit Digits"), 'on_change_with_unit_digits') service_unit_category = fields.Function( fields.Many2One('product.uom.category', "Service Unit Category"), 'on_change_with_service_unit_category') unit_price = fields.Numeric( "Unit Price", digits=price_digits, states={ 'readonly': Eval('subscription_state') != 'draft', }, depends=['subscription_state']) consumption_recurrence = fields.Many2One( 'sale.subscription.recurrence.rule.set', "Consumption Recurrence", states={ 'readonly': Eval('subscription_state') != 'draft', }, depends=['subscription_state']) consumption_delay = fields.TimeDelta( "Consumption Delay", states={ 'readonly': Eval('subscription_state') != 'draft', 'invisible': ~Eval('consumption_recurrence'), }, depends=['subscription_state', 'consumption_recurrence']) next_consumption_date = fields.Date("Next Consumption Date", readonly=True) next_consumption_date_delayed = fields.Function( fields.Date("Next Consumption Delayed"), 'get_next_consumption_date_delayed') consumed = fields.Boolean("Consumed") start_date = fields.Date( "Start Date", domain=['OR', ('start_date', '>=', Eval('subscription_start_date')), ('start_date', '=', None), ], states={ 'readonly': ((Eval('subscription_state') != 'draft') | Eval('consumed')), }, depends=['subscription_start_date', 'subscription_state', 'consumed']) end_date = fields.Date( "End Date", domain=['OR', [ If(Bool(Eval('subscription_end_date')), ('end_date', '<=', Eval('subscription_end_date')), ('end_date', '>=', Eval('start_date'))), If(Bool(Eval('next_consumption_date')), ('end_date', '>=', Eval('next_consumption_date')), ('end_date', '>=', Eval('start_date'))), ], ('end_date', '=', None), ], states={ 'readonly': ((Eval('subscription_state') != 'draft') | (~Eval('next_consumption_date') & Eval('consumed'))), }, depends=['subscription_end_date', 'start_date', 'next_consumption_date', 'subscription_state', 'consumed']) @fields.depends('subscription', '_parent_subscription.state') def on_change_with_subscription_state(self, name=None): if self.subscription: return self.subscription.state @fields.depends('subscription', '_parent_subscription.start_date') def on_change_with_subscription_start_date(self, name=None): if self.subscription: return self.subscription.start_date @fields.depends('subscription', '_parent_subscription.end_date') def on_change_with_subscription_end_date(self, name=None): if self.subscription: return self.subscription.end_date @classmethod def default_quantity(cls): return 1 @fields.depends('unit') def on_change_with_unit_digits(self, name=None): if self.unit: return self.unit.digits return 2 @fields.depends('service') def on_change_with_service_unit_category(self, name=None): if self.service: return self.service.product.default_uom_category.id @fields.depends('service', 'quantity', 'unit', 'description', 'subscription', '_parent_subscription.currency', '_parent_subscription.party', '_parent_subscription.start_date') def on_change_service(self): pool = Pool() Product = pool.get('product.product') if not self.service: self.consumption_recurrence = None self.consumption_delay = None return party = None party_context = {} if self.subscription and self.subscription.party: party = self.subscription.party if party.lang: party_context['language'] = party.lang.code product = self.service.product category = product.sale_uom.category if not self.unit or self.unit.category != category: self.unit = product.sale_uom self.unit_digits = product.sale_uom.digits with Transaction().set_context(self._get_context_sale_price()): self.unit_price = Product.get_sale_price( [product], self.quantity or 0)[product.id] if self.unit_price: self.unit_price = self.unit_price.quantize( Decimal(1) / 10 ** self.__class__.unit_price.digits[1]) if not self.description: with Transaction().set_context(party_context): self.description = Product(product.id).rec_name self.consumption_recurrence = self.service.consumption_recurrence self.consumption_delay = self.service.consumption_delay def _get_context_sale_price(self): context = {} if getattr(self, 'subscription', None): if getattr(self.subscription, 'currency', None): context['currency'] = self.subscription.currency.id if getattr(self.subscription, 'party', None): context['customer'] = self.subscription.party.id if getattr(self.subscription, 'start_date', None): context['sale_date'] = self.subscription.start_date if self.unit: context['uom'] = self.unit.id elif self.service: context['uom'] = self.service.sale_uom.id # TODO tax return context def get_next_consumption_date_delayed(self, name=None): if self.next_consumption_date and self.consumption_delay: return self.next_consumption_date + self.consumption_delay return self.next_consumption_date @classmethod def default_consumed(cls): return False @classmethod def domain_next_consumption_date_delayed(cls, domain, tables): field = cls.next_consumption_date_delayed._field table, _ = tables[None] name, operator, value = domain Operator = fields.SQL_OPERATORS[operator] column = ( table.next_consumption_date + Coalesce( table.consumption_delay, datetime.timedelta())) expression = Operator(column, field._domain_value(operator, value)) if isinstance(expression, operators.In) and not expression.right: expression = Literal(False) elif isinstance(expression, operators.NotIn) and not expression.right: expression = Literal(True) expression = field._domain_add_null( column, operator, value, expression) return expression @classmethod def generate_consumption(cls, date=None): pool = Pool() Date = pool.get('ir.date') Consumption = pool.get('sale.subscription.line.consumption') Subscription = pool.get('sale.subscription') if date is None: date = Date.today() remainings = all_lines = cls.search([ ('consumption_recurrence', '!=', None), ('next_consumption_date_delayed', '<=', date), ('subscription.state', '=', 'running'), ]) consumptions = [] subscription_ids = set() while remainings: lines, remainings = remainings, [] for line in lines: consumptions.append( line.get_consumption(line.next_consumption_date)) line.next_consumption_date = ( line.compute_next_consumption_date()) line.consumed = True if line.next_consumption_date is None: subscription_ids.add(line.subscription.id) elif line.get_next_consumption_date_delayed() <= date: remainings.append(line) Consumption.save(consumptions) cls.save(all_lines) Subscription.process(Subscription.browse(list(subscription_ids))) def get_consumption(self, date): pool = Pool() Consumption = pool.get('sale.subscription.line.consumption') return Consumption(line=self, quantity=self.quantity, date=date) def compute_next_consumption_date(self): if not self.consumption_recurrence: return None start_date = self.start_date or self.subscription.start_date date = self.next_consumption_date or start_date rruleset = self.consumption_recurrence.rruleset(start_date) dt = datetime.datetime.combine(date, datetime.time()) inc = (start_date == date) and not self.next_consumption_date next_date = rruleset.after(dt, inc=inc).date() for end_date in [self.end_date, self.subscription.end_date]: if end_date: if next_date > end_date: return None return next_date @classmethod def copy(cls, lines, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('consumed') return super(Line, cls).copy(lines, default=default)