def get_change_subject(self, ar, cw): ctx = dict(user=ar.user, what=str(self)) if cw is None: return _("{user} submitted ticket {what}").format(**ctx) if len(list(cw.get_updates())) == 0: return return _("{user} modified {what}").format(**ctx)
def objects(): yield lib_objects() GuestRole = rt.models.cal.GuestRole yield GuestRole(**dd.str2kw('name', _("Participant"))) yield GuestRole(**dd.str2kw('name', _("Guide"))) yield GuestRole(**dd.str2kw('name', _("Teacher"))) EventType = rt.models.cal.EventType RecurrentEvent = rt.models.cal.RecurrentEvent Recurrencies = rt.models.cal.Recurrencies DEMO_START_YEAR = rt.models.cal.DEMO_START_YEAR holidays = EventType.objects.get( **dd.str2kw('name', _("Holidays"))) yield RecurrentEvent( event_type=holidays, every_unit=Recurrencies.yearly, monday=True, tuesday=True, wednesday=True, thursday=True, friday=True, saturday=True, sunday=True, every=1, start_date=datetime.date( year=DEMO_START_YEAR, month=7, day=1), end_date=datetime.date( year=DEMO_START_YEAR, month=8, day=31), **dd.str2kw('name', _("Summer holidays")))
def objects(): UserTypes = rt.models.users.UserTypes Company = rt.models.contacts.Company Product = rt.models.products.Product Account = rt.models.accounts.Account AccountTypes = rt.models.accounts.AccountTypes CommonAccounts = rt.models.accounts.CommonAccounts yield create_user("daniel", UserTypes.therapist) yield create_user("elmar", UserTypes.therapist) yield create_user("lydia", UserTypes.secretary) # yield skills_objects() obj = Company( name="Tough Thorough Thought Therapies", country_id="BE", vat_id="BE12 3456 7890") yield obj settings.SITE.site_config.update(site_company=obj) yield named(Product, _("Group therapy"), sales_price=30) indacc = named( Account, _("Sales on individual therapies"), group=CommonAccounts.sales.get_object().group, type=AccountTypes.incomes, ref="7010") yield indacc yield named( Product, _("Individual therapy"), sales_price=60, sales_account=indacc) yield named(Product, _("Other"), sales_price=35)
def update_widgets_for(ar, user): available = set([i.name for i in get_available_items(user)]) Widget = rt.models.dashboard.Widget qs = Widget.objects.filter(user=user).order_by('seqno') def ok(ar): seqno = 0 for w in qs: if w.item_name in available: available.remove(w.item_name) seqno = max(seqno, w.seqno) else: w.delete() up = user.get_preferences() for name in available: seqno += 1 obj = Widget(user=user, item_name=name, seqno=seqno) obj.full_clean() obj.save() # print(20161128, "created item", obj) up.invalidate() ar.set_response(refresh=True) if qs.count() > 0: ar.confirm( ok, _("This will overwrite your current settings."), _("Are you sure?")) else: ok(ar)
def run_from_ui(self, ar, **kw): if len(ar.selected_rows) == 1: obj = ar.selected_rows[0] bm = obj.get_build_method() mf = bm.get_target(self, obj) leaf = mf.parts[-1] if obj.build_time is None: obj.build_target(ar) ar.info("%s has been built.", leaf) else: ar.info("Reused %s from cache.", leaf) url = mf.get_url(ar.request) self.notify_done(ar, bm, leaf, url, **kw) ar.set_response(refresh=True) return def ok(ar2): # qs = [ar.actor.get_row_by_pk(pk) for pk in ar.selected_pks] mf = self.print_multiple(ar, ar.selected_rows) ar2.success(open_url=mf.get_url(ar.request)) # kw.update(refresh_all=True) # return kw msg = _("This will print %d rows.") % len(ar.selected_rows) ar.confirm(ok, msg, _("Are you sure?"))
def run_from_ui(self, ar): elem = ar.selected_rows[0] def doit(ar): elem.clear_cache() ar.success(_("%s printable cache has been cleared.") % elem, refresh=True) t = elem.get_cache_mtime() if t is not None: # set microseconds to those of the stored field because # Django DateTimeField can have microseconds precision or # not depending on the database backend. t = datetime.datetime( t.year, t.month, t.day, t.hour, t.minute, t.second, elem.build_time.microsecond) if settings.USE_TZ: t = make_aware(t) if t != elem.build_time: # logger.info("20140313 %r != %r", t, elem.build_time) return ar.confirm( doit, _("This will discard all changes in the generated file."), _("Are you sure?")) return doit(ar)
def skills_objects(): "was previously in skills.fixtures.demo2" Skill = rt.models.skills.Skill Competence = rt.models.skills.Competence Demand = rt.models.skills.Demand # Ticket = rt.models.tickets.Ticket User = rt.models.users.User yield named(Skill, _('Psychotherapy')) yield named(Skill, _('Psychiatry')) SKILLS = Cycler(Skill.objects.all()) END_USERS = Cycler(dd.plugins.skills.end_user_model.objects.all()) i = 0 for j in range(2): for u in User.objects.all(): i += 1 yield Competence(user=u, faculty=SKILLS.pop()) if i % 2: yield Competence(user=u, faculty=SKILLS.pop()) if i % 3: yield Competence( user=u, faculty=SKILLS.pop(), end_user=END_USERS.pop()) for i, t in enumerate( dd.plugins.skills.demander_model.objects.all()): yield Demand(demander=t, skill=SKILLS.pop()) if i % 3: yield Demand(demander=t, skill=SKILLS.pop())
def get_checkdata_problems(self, obj, fix=False): if obj.event_type_id and obj.event_type.force_guest_states: if obj.state.guest_state: qs = obj.guest_set.exclude(state=obj.state.guest_state) if qs.exists(): msg = _("Some guests have another state than {0}") yield (True, msg.format(obj.state.guest_state)) if fix: for g in qs: g.state = obj.state.guest_state g.full_clean() g.save() if not obj.state.edit_guests: return # existing = set([g.partner.pk for g in obj.guest_set.all()]) # if len(existing) == 0: guests = obj.guest_set.all() if not guests.exists(): suggested = list(obj.suggest_guests()) if len(suggested) > 0: msg = _("No participants although {0} suggestions exist.") yield (True, msg.format(len(suggested))) if fix: for g in suggested: g.save()
def description(cls, fld, ar): if ar is None: return '' elems = [fld.help_text, E.br()] def x(label, lst, xlst): if lst is None: return spec = ' '.join([i.name or i.value for i in lst]) if xlst is not None: spec += ' ' + ' '.join([ "!"+(i.name or i.value) for i in xlst]) spec = spec.strip() if spec: elems.extend([label, " ", spec, E.br()]) x(_("columns"), fld.vat_columns, fld.exclude_vat_columns) x(_("regimes"), fld.vat_regimes, fld.exclude_vat_regimes) x(_("classes"), fld.vat_classes, fld.exclude_vat_classes) elems += [ fld.__class__.__name__, ' ', DCLABELS[fld.dc], "" if fld.both_dc else " only", E.br()] if fld.observed_fields: elems += [ _("Sum of"), ' ', ' '.join([i.name for i in fld.observed_fields]), E.br()] return E.div(*forcetext(elems))
def get_notify_message(self, ar, cw): """Returns the text of the notification message to emit. The default implementation returns a message of style "{object} has been modified by {user}" followed by a summary of the changes. Application code can override this. Returning None or an empty string means to suppress notification. """ if cw is None: return items = list(cw.get_updates_html()) if len(items) == 0: return elems = [E.p( ar.obj2html(self), ' ', _("has been modified by {user}").format( user=ar.get_user()), ":")] elems.append(E.ul(*items)) if False: elems.append(E.p(_( "Subsequent changes to {obj} will not be notified " "until you visit {url} and mark this notification " "as seen.").format( url=settings.SITE.server_url or "Lino", obj=self.get_notify_owner(ar)))) return E.tostring(E.div(*elems))
def run_from_ui(self, ar, **kw): lcd = settings.SITE.confdirs.LOCAL_CONFIG_DIR if lcd is None: # ar.info("No local config directory in %s " % # settings.SITE.confdirs) raise Warning("No local config directory. " "Contact your system administrator.") elem = ar.selected_rows[0] bm = elem.get_build_method() leaf = bm.get_template_leaf(self, elem) filename = bm.get_template_file(ar, self, elem) local_file = None groups = elem.get_template_groups() assert len(groups) > 0 for grp in reversed(groups): # subtle: if there are more than 1 groups parts = [grp, leaf] local_file = os.path.join(lcd.name, *parts) if filename == local_file: break parts = ['webdav', 'config'] + parts url = settings.SITE.build_media_url(*parts) # url = ar.build_webdav_uri(url) if not (settings.SITE.webdav_protocol or has_davlink): msg = "cp %s %s" % (filename, local_file) ar.info(msg) raise Warning("WebDAV is not enabled. " "Contact your system administrator.") def doit(ar): ar.info("Going to open url: %s " % url) if settings.SITE.webdav_protocol: ar.success(open_url=url) else: ar.success(open_webdav_url=url) # logger.info('20140313 EditTemplate %r', kw) if filename == local_file: doit(ar) else: ar.info("Gonna copy %s to %s", filename, local_file) def ok(ar2): logger.info( "%s made local template copy %s", ar.user, local_file) rt.makedirs_if_missing(os.path.dirname(local_file)) shutil.copy(filename, local_file) doit(ar2) msg = _( "Before you can edit this template we must create a " "local copy on the server. " "This will exclude the template from future updates.") ar.confirm(ok, msg, _("Are you sure?"))
def get_title_tags(self, ar): for t in super(Pupil, self).get_title_tags(ar): yield t pv = ar.param_values if pv.show_members: yield "{0}:{1}".format(_("Members"), pv.show_members) if pv.show_ckk: yield "{0}:{1}".format(_("CKK"), pv.show_ckk)
def objects(): Account = rt.modules.accounts.Account JournalGroups = rt.modules.ledger.JournalGroups BankStatement = rt.modules.finan.BankStatement PaymentOrder = rt.modules.finan.PaymentOrder DisbursementOrdersByJournal = rt.modules.finan.DisbursementOrdersByJournal InvoicesByJournal = rt.modules.vatless.InvoicesByJournal ProjectInvoicesByJournal = rt.modules.vatless.ProjectInvoicesByJournal MatchRule = rt.modules.ledger.MatchRule a4400 = Account.objects.get(ref="4400") a4450 = Account.objects.get(ref="4450") a5800 = Account.objects.get(ref="5800") kw = dict(journal_group=JournalGroups.reg) kw.update(trade_type='purchases', ref="REG") kw.update(dd.str2kw('name', _("Incoming invoices"))) kw.update(dc=CREDIT) yield ProjectInvoicesByJournal.create_journal(**kw) kw.update(ref="SREG") kw.update(dd.str2kw('name', _("Collective purchase invoices"))) yield InvoicesByJournal.create_journal(**kw) kw.update(dd.str2kw('name', _("Disbursement orders"))) kw.update(account='4450', ref="AAW") kw.update(journal_group=JournalGroups.anw) # kw.update(dc=CREDIT) # kw.update(invert_due_dc=False) jnl = DisbursementOrdersByJournal.create_journal(**kw) yield jnl yield MatchRule(journal=jnl, account=a4400) if dd.is_installed('client_vouchers'): ClientVoucher = rt.modules.client_vouchers.ClientVoucher kw = dict(journal_group=JournalGroups.aids) kw.update(trade_type='aids', ref="AIDS") kw.update(dd.str2kw('name', _("Aid allocations"))) jnl = ClientVoucher.create_journal(**kw) yield jnl yield MatchRule(journal=jnl, account=a4400) kw = dict() # kw.update(journal_group=JournalGroups.tre) # kw.update(dd.str2kw('name', _("KBC"))) # kw.update(account='5500', ref="KBC") # jnl = BankStatement.create_journal(**kw) # yield jnl # yield MatchRule(journal=jnl, account=a4450) # yield MatchRule(journal=jnl, account=a5800) kw.update(journal_group=JournalGroups.zau) kw.update(dd.str2kw('name', _("KBC Payment Orders"))) kw.update(account='5800', ref="ZKBC") kw.update(dc=CREDIT) jnl = PaymentOrder.create_journal(**kw) yield jnl yield MatchRule(journal=jnl, account=a4450)
def get_change_subject(self, ar, cw): ctx = dict(user=ar.user, what=str(self)) if cw is None: return _("{user} created {what}").format(**ctx) # msg = _("has been created by {user}").format(**ctx) # return "{} {}".format(self, msg) if len(list(cw.get_updates())) == 0: return return _("{user} modified {what}").format(**ctx)
def get_detail_title(self, ar, obj): """Overrides the default beaviour """ me = ar.get_user() pk = obj.votable.pk if me == obj.user: return _("My vote about #{}").format(pk) else: return _("{}'s vote about #{}").format(obj.user, pk)
def get_checkdata_problems(self, obj, fix=False): if not obj.has_conflicting_events(): return qs = obj.get_conflicting_events() num = qs.count() if num == 1: msg = _("Event conflicts with {0}.").format(qs[0]) else: msg = _("Event conflicts with {0} other events.").format(num) yield (False, msg)
def get_title_tags(self, ar): pv = ar.param_values if pv.start_period: if pv.end_period: yield _("Periods {}...{}").format( pv.start_period, pv.end_period) else: yield _("Period {}").format(pv.start_period) else: yield str(_("All periods"))
def get_checkdata_problems(self, obj, fix=False): if obj.account_id: if obj.account.needs_partner: if not obj.partner_id: if obj.voucher.journal.refuse_missing_partner(): yield (False, _("Account {} needs a partner")) else: if obj.partner_id: yield (False, _("Account {} cannot be used with a partner"))
def objects(): PaperType = rt.models.sales.PaperType bm = rt.models.printing.BuildMethods.get_system_default() yield PaperType( template="DefaultLetter" + bm.template_ext, **dd.str2kw('name', _("Letter paper"))) yield PaperType( template="DefaultBlank" + bm.template_ext, **dd.str2kw('name', _("Blank paper")))
def override_field_names(sender=None, **kwargs): for m in rt.models_by_base(FinancialVoucher): dd.update_field( m, 'narration', verbose_name=_("Internal reference")) dd.update_field( m, 'item_remark', verbose_name=_("External reference")) for m in rt.models_by_base(FinancialVoucherItem): # dd.update_field( # m, 'narration', verbose_name=_("Internal reference")) dd.update_field( m, 'remark', verbose_name=_("External reference"))
def get_parameter_fields(cls, **fields): fields.update( show_members=dd.YesNo.field( _("Members"), blank=True, help_text=_( "Show those whose 'Member until' is after today.")), show_ckk=dd.YesNo.field(_("CKK"), blank=True), show_lfv=dd.YesNo.field(_("LFV"), blank=True), show_raviva=dd.YesNo.field(_("Raviva"), blank=True)) return super(Pupil, cls).get_parameter_fields(**fields)
def full_clean(self, *args, **kw): if self.periods <= 0: raise ValidationError(_("Periods must be > 0")) if self.distribute and self.monthly_rate: raise ValidationError( # _("Cannot set both 'Distribute' and 'Monthly rate'")) _("Cannot set 'Distribute' when 'Monthly rate' is %r") % self.monthly_rate) # self.account_type = self.account.type # if not self.account_type: # ~ raise ValidationError(_("Budget entry #%d has no account_type") % obj2unicode(self)) super(Entry, self).full_clean(*args, **kw)
def objects(): Group = rt.models.groups.Group User = rt.models.users.User UserTypes = rt.models.users.UserTypes yield named(Group, _("Hitchhiker's Guide to the Galaxy")) yield named(Group, _("Star Trek")) yield named(Group, _("Harry Potter")) yield User(username="******", user_type=UserTypes.user) yield User(username="******", user_type=UserTypes.user) yield User(username="******", user_type=UserTypes.user)
def get_actors(self): """Return a list of the actors of this budget.""" attname = "_cached_actors" if hasattr(self, attname): return getattr(self, attname) l = list(self.actor_set.all()) if len(l) > 0: main_header = _("Common") else: main_header = _("Amount") l.insert(0, MainActor(self, main_header)) setattr(self, attname, l) return l
def get_rfc_description(self, ar): html = '' _ = gettext if self.description: # html += tostring(E.b(_("Description"))) html += ar.parse_memo(self.description) if self.upgrade_notes: html += tostring(E.b(_("Resolution"))) + ": " html += ar.parse_memo(self.upgrade_notes) if self.duplicate_of_id: html += tostring(_("Duplicate of")) + " " html += tostring(self.duplicate_of.obj2href(ar)) return html
def before_build(self, bm, elem): # if not elem.execution_date: # raise Warning(_("You must specify an execution date")) acc = elem.journal.sepa_account if not acc: raise Warning( _("Journal {} has no SEPA account").format(elem.journal)) if not acc.bic: raise Warning( _("SEPA account for journal {} has no BIC").format( elem.journal)) return super(WritePaymentsInitiation, self).before_build(bm, elem)
def run_from_ui(self, ar, **kw): pv = ar.action_param_values user = auth.authenticate( ar.request, username=pv.username, password=pv.password) if user is None: ar.error(_("Failed to log in as {}.".format(pv.username))) else: # user.is_authenticated: auth.login(ar.request, user) ar.success( _("Now logged in as {}").format(user), close_window=True, goto_url=ar.renderer.plugin.build_plain_url())
def run_from_ui(self, ar, **kw): obj = ar.selected_rows[0] assert obj is None def ok(ar): for obj in ar: obj.state = EnrolmentStates.confirmed obj.save() ar.set_response(refresh_all=True) msg = _( "This will confirm all %d enrolments in this list.") % ar.get_total_count() ar.confirm(ok, msg, _("Are you sure?"))
def run_from_ui(self, ar, **kw): def ok(ar2): now = timezone.now() for obj in self.get_sessions(ar): self.end_session(ar, obj, now) ar2.set_response(refresh=True) if True: ok(ar) else: msg = _("Close {0} sessions.").format(len(ar.selected_rows)) ar.confirm(ok, msg, _("Are you sure?"))
def setup_parameters(cls, fields): fields.update( ticket_user=dd.ForeignKey( settings.SITE.user_model, verbose_name=_("Author"), blank=True, null=True, help_text=_( "Only rows on votables whose author is this user.")), exclude_ticket_user=dd.ForeignKey( settings.SITE.user_model, verbose_name=_("Exclude author"), blank=True, null=True, help_text=_( "Only rows on votables whose author is not this user.")), # show_todo=dd.YesNo.field(_("To do"), blank=True), # vote_view=VoteViews.field(blank=True), state=VoteStates.field( blank=True, help_text=_("Only rows having this state.")), mail_mode=MailModes.field( blank=True, help_text=_("Only rows having this mail mode."))) fld = config.votable_model._meta.get_field('state') hlp = lazy_format( _("Only rows whose {model} has this state."), model=config.votable_model._meta.verbose_name) lbl = lazy_format( _("{model} state"), model=config.votable_model._meta.verbose_name) fields.update( votable_state=fld.choicelist.field( lbl, blank=True, help_text=hlp)) super(Vote, cls).setup_parameters(fields)
class Meta: app_label = 'finan' abstract = dd.is_abstract_model(__name__, 'PaymentOrder') verbose_name = _("Payment Order") verbose_name_plural = _("Payment Orders")
class Meta: app_label = 'ledger' verbose_name = _("Match rule") verbose_name_plural = _("Match rules") unique_together = ['account', 'journal']
class Meta: app_label = 'finan' verbose_name = _("Journal Entry item") verbose_name_plural = _("Journal Entry items")
class Meta: app_label = 'cal' verbose_name = _("Task") verbose_name_plural = _("Tasks") abstract = dd.is_abstract_model(__name__, 'Task')
class Meta: app_label = 'finan' abstract = dd.is_abstract_model(__name__, 'JournalEntry') verbose_name = _("Journal Entry") verbose_name_plural = _("Journal Entries")
class Meta: app_label = 'cal' abstract = dd.is_abstract_model(__name__, 'Event') # abstract = True verbose_name = _("Calendar entry") verbose_name_plural = _("Calendar entries")
class Meta: app_label = 'cal' verbose_name = _("Recurring event") verbose_name_plural = _("Recurring events") abstract = dd.is_abstract_model(__name__, 'RecurrentEvent')
class Meta: app_label = 'ledger' verbose_name = _("Payment term") verbose_name_plural = _("Payment terms")
class Meta: verbose_name = _("Account") verbose_name_plural = _("Accounts") ordering = ['ref'] app_label = 'ledger'
class Meta: app_label = 'ledger' verbose_name = _("Accounting period") verbose_name_plural = _("Accounting periods") ordering = ['ref']
class Meta: app_label = 'ledger' verbose_name = _("Fiscal year") verbose_name_plural = _("Fiscal years") ordering = ['ref']
class AccountingPeriod(DateRange, Referrable): class Meta: app_label = 'ledger' verbose_name = _("Accounting period") verbose_name_plural = _("Accounting periods") ordering = ['ref'] preferred_foreignkey_width = 10 state = PeriodStates.field(default='open') year = dd.ForeignKey('ledger.FiscalYear', blank=True, null=True) remark = models.CharField(_("Remark"), max_length=250, blank=True) @classmethod def get_available_periods(cls, entry_date): """Return a queryset of periods available for booking.""" if entry_date is None: # added 20160531 entry_date = dd.today() fkw = dict(start_date__lte=entry_date, end_date__gte=entry_date) return rt.models.ledger.AccountingPeriod.objects.filter(**fkw) @classmethod def get_ref_for_date(cls, d): """Return a text to be used as :attr:`ref` for a new period. Alternative implementation for usage on a site with movements before year 2000:: @classmethod def get_ref_for_date(cls, d): if d.year < 2000: y = str(d.year - 1900) elif d.year < 2010: y = "A" + str(d.year - 2000) elif d.year < 2020: y = "B" + str(d.year - 2010) elif d.year < 2030: y = "C" + str(d.year - 2020) return y + "{:0>2}".format(d.month) """ y = FiscalYear.year2ref(d.year) return "{}-{:0>2}".format(y, d.month) # if dd.plugins.ledger.fix_y2k: # return rt.models.ledger.FiscalYear.from_int(d.year).ref \ # + "{:0>2}".format(d.month) # return "{0.year}-{0.month:0>2}".format(d) # """The template used for building the :attr:`ref` of an # :class:`AccountingPeriod`. # # `Format String Syntax # <https://docs.python.org/2/library/string.html#formatstrings>`_ # # """ @classmethod def get_periods_in_range(cls, p1, p2): return cls.objects.filter(ref__gte=p1.ref, ref__lte=p2.ref) @classmethod def get_period_filter(cls, voucher_prefix, p1, p2, **kwargs): if p1 is None: return kwargs # ignore preliminary movements if a start_period is given: kwargs[voucher_prefix + "journal__preliminary"] = False accounting_period = voucher_prefix + "accounting_period" if p2 is None: kwargs[accounting_period] = p1 else: periods = cls.get_periods_in_range(p1, p2) kwargs[accounting_period + '__in'] = periods return kwargs @classmethod def get_default_for_date(cls, d): ref = cls.get_ref_for_date(d) obj = rt.models.ledger.AccountingPeriod.get_by_ref(ref, None) if obj is None: values = dict(start_date=d.replace(day=1)) values.update(end_date=last_day_of_month(d)) values.update(ref=ref) obj = AccountingPeriod(**values) obj.full_clean() obj.save() return obj def full_clean(self, *args, **kwargs): if self.start_date is None: self.start_date = dd.today().replace(day=1) if not self.year: self.year = FiscalYear.get_or_create_from_date(self.start_date) super(AccountingPeriod, self).full_clean(*args, **kwargs) def __str__(self): if not self.ref: return dd.obj2str(self) # "{0} {1} (#{0})".format(self.pk, self.year) return self.ref
def disable_voucher_delete(self, doc): # print "pre_delete_voucher", doc.number, self.get_next_number() if self.force_sequence: if doc.number + 1 != self.get_next_number(doc): return _("%s is not the last voucher in journal" % str(doc))
class VoucherChecker(Checker): verbose_name = _("Check integrity of ledger vouchers") messages = dict( missing=_("Missing movement {0}."), unexpected=_("Unexpected movement {0}."), diff=_("Movement {0} : {1}"), warning=_("Failed to get movements for {0} : {1}"), ) def get_checkable_models(self): return rt.models_by_base(Voucher) # also MTI children def get_checkdata_problems(self, obj, fix=False): if obj.__class__ is rt.models.ledger.Voucher: if obj.get_mti_leaf() is None: yield (True, _("Voucher without MTI leaf")) if fix: obj.movement_set.all().delete() obj.delete() return def m2k(obj): return obj.seqno wanted = dict() # if obj.state in dd.plugins.ledger.registered_states: if not obj.state.is_editable: seqno = 0 fcu = dd.plugins.ledger.suppress_movements_until try: for m in obj.get_wanted_movements(): if fcu and m.value_date <= fcu: continue seqno += 1 m.seqno = seqno # if fcu and m.value_date <= fcu: # m.cleared = True m.full_clean() wanted[m2k(m)] = m except Warning as e: yield (False, self.messages['warning'].format(obj, e)) return for em in obj.movement_set.order_by('seqno'): wm = wanted.pop(m2k(em), None) if wm is None: yield (False, self.messages['unexpected'].format(em)) return diffs = [] for k in ('partner_id', 'account_id', 'dc', 'amount', 'value_date'): emv = getattr(em, k) wmv = getattr(wm, k) if emv != wmv: diffs.append(u"{} ({}!={})".format(k, emv, wmv)) if len(diffs) > 0: yield (False, self.messages['diff'].format(em, u', '.join(diffs))) return if wanted: for missing in wanted.values(): yield (False, self.messages['missing'].format(missing)) return
class Meta: app_label = 'finan' abstract = dd.is_abstract_model(__name__, 'BankStatement') verbose_name = _("Bank Statement") verbose_name_plural = _("Bank Statements")
def objects(): DPR = rt.models.calview.DailyPlannerRow yield DPR(end_time="12:00", **dd.str2kw('designation', _("AM"))) yield DPR(start_time="12:00", **dd.str2kw('designation', _("PM"))) yield DPR(**dd.str2kw('designation', _("All day")))
class Meta: app_label = 'cal' abstract = dd.is_abstract_model(__name__, 'RemoteCalendar') verbose_name = _("Remote Calendar") verbose_name_plural = _("Remote Calendars") ordering = ['seqno']
class Plugin(ad.Plugin): verbose_name = _("Virtual tables") def setup_main_menu(self, site, profile, m): m = m.add_menu(self.app_label, self.verbose_name) m.add_action('vtables.CitiesAndInhabitants')
class Event(Component, Ended, Assignable, TypedPrintable, Mailable, Postable, Publishable): class Meta: app_label = 'cal' abstract = dd.is_abstract_model(__name__, 'Event') # abstract = True verbose_name = _("Calendar entry") verbose_name_plural = _("Calendar entries") # verbose_name = pgettext("cal", "Event") # verbose_name_plural = pgettext("cal", "Events") publisher_location = "cal" publisher_template = "cal/event.pub.html" update_guests = UpdateGuests() update_events = UpdateEntriesByEvent() show_today = ShowEntriesByDay('start_date') event_type = dd.ForeignKey('cal.EventType', blank=True, null=True) transparent = models.BooleanField(_("Transparent"), default=False) room = dd.ForeignKey('cal.Room', null=True, blank=True) # priority = dd.ForeignKey(Priority, null=True, blank=True) state = EntryStates.field(default=EntryStates.as_callable('suggested')) all_day = ExtAllDayField(_("all day")) move_next = MoveEntryNext() show_conflicting = dd.ShowSlaveTable(ConflictingEvents) allow_merge_action = False @classmethod def setup_parameters(cls, params): params.update( mixins.ObservedDateRange( user=dd.ForeignKey(settings.SITE.user_model, verbose_name=_("Managed by"), blank=True, null=True), event_type=dd.ForeignKey('cal.EventType', blank=True, null=True), room=dd.ForeignKey('cal.Room', blank=True, null=True), project=dd.ForeignKey(settings.SITE.project_model, blank=True, null=True), assigned_to=dd.ForeignKey(settings.SITE.user_model, verbose_name=_("Assigned to"), blank=True, null=True), state=EntryStates.field(blank=True), # unclear = models.BooleanField(_("Unclear events")) observed_event=EventEvents.field(blank=True), show_appointments=dd.YesNo.field(_("Appointments"), blank=True), presence_guest=dd.ForeignKey(dd.plugins.cal.partner_model, verbose_name=_("Guest"), blank=True, null=True))) if settings.SITE.project_model: params['project'].help_text = format_lazy( _("Show only entries having this {project}."), project=settings.SITE.project_model._meta.verbose_name) return params # params_layout = """ # start_date end_date observed_event state # user assigned_to project event_type room show_appointments # """ # cal_params_layout = """user event_type room project presence_guest""" @classmethod def calendar_param_filter(cls, qs, pv): if pv.user: qs = qs.filter(user=pv.user) if pv.assigned_to: qs = qs.filter(assigned_to=pv.assigned_to) if settings.SITE.project_model is not None and pv.project: qs = qs.filter(project=pv.project) if pv.event_type: qs = qs.filter(event_type=pv.event_type) else: if pv.show_appointments == dd.YesNo.yes: qs = qs.filter(event_type__is_appointment=True) elif pv.show_appointments == dd.YesNo.no: qs = qs.filter(event_type__is_appointment=False) if pv.state: qs = qs.filter(state=pv.state) if pv.room: qs = qs.filter(room=pv.room) if pv.observed_event == EventEvents.stable: qs = qs.filter(state__in=set(EntryStates.filter(fixed=True))) elif pv.observed_event == EventEvents.pending: qs = qs.filter(state__in=set(EntryStates.filter(fixed=False))) # multi-day entries should appear on each day if pv.start_date: c1 = Q(start_date__gte=pv.start_date) c2 = Q(start_date__lt=pv.start_date, end_date__isnull=False, end_date__gte=pv.start_date) qs = qs.filter(c1 | c2) if pv.end_date: c1 = Q(end_date__isnull=True, start_date__lte=pv.end_date) c2 = Q(end_date__isnull=False, end_date__lte=pv.end_date) qs = qs.filter(c1 | c2) qs = qs.filter( Q(end_date__isnull=True, start_date__lte=pv.end_date) | Q(end_date__gte=pv.end_date)) if pv.presence_guest: qs = qs.filter( Q(guest__partner=pv.presence_guest) | Q(event_type__all_rooms=True)) return qs def strftime(self): if not self.start_date: return '' d = self.start_date.strftime(settings.SITE.date_format_strftime) if self.start_time: t = self.start_time.strftime(settings.SITE.time_format_strftime) return "%s %s" % (d, t) else: return d def get_diplay_color(self): if self.room: return self.room.display_color def calendar_fmt(self, pv): # if pv.user: # if pv.assigned_to: # if settings.SITE.project_model is not None and pv.project: # if pv.event_type: t = [] if self.start_time: t.append(str(self.start_time)[:5]) # elif not pv.start_date: # t.append(str(self.start_date)) if not pv.user and self.user: t.append(str(self.user)) if self.summary: t.append(self.summary) if not pv.event_type and self.event_type: t.append(str(self.event_type)) if not pv.room and self.room: t.append(str(self.room)) if settings.SITE.project_model is not None and not pv.project and self.project: t.append(str(self.project)) # if u is None: # return "{} {}".format(t, self.room) if self.room else t # u = u.initials or u.username or str(u) return E.span(" ".join(t)) # return "{} {}".format(t, u) def colored_calendar_fmt(self, pv): ele = E.span(self.calendar_fmt(pv)) data_color = self.get_diplay_color() if data_color: dot = E.span(u"\u00A0", CLASS="dot") # ele.attrib['style'] = "color: white;background-color: {};".format(data_color) dot.attrib['style'] = "background-color: {};".format(data_color) return E.div(*[dot, ele]) else: return E.div(*[ele]) def __str__(self): if self.summary: s = self.summary elif self.event_type: s = str(self.event_type) elif self.pk: s = self._meta.verbose_name + " #" + str(self.pk) else: s = _("Unsaved %s") % self._meta.verbose_name when = self.strftime() if when: s = "{} ({})".format(s, when) # u = self.user # if u is None and self.room: # u = self.room # if u is None: # return '%s object (%s)' % (self.__class__.__name__, self.pk) # u = u.initials or u.username or str(u) # s = "{} ({})".format(s, u) return s # if e.start_time: # t = str(e.start_time)[:5] # else: # t = str(e.event_type) # u = e.user # if u is None: # return "{} {}".format(t, e.room) if e.room else t # u = u.initials or u.username or str(u) # return "{} {}".format(t, u) def duration_veto(obj): if obj.end_date is None: return et = obj.event_type if et is None: return duration = obj.end_date - obj.start_date # print (20161222, duration.days, et.max_days) if et.max_days and duration.days > et.max_days: return _("Event lasts {0} days but only {1} are allowed.").format( duration.days, et.max_days) def full_clean(self, *args, **kw): super(Event, self).full_clean(*args, **kw) et = self.event_type if et is not None and et.default_duration is not None: assert isinstance(et.default_duration, Duration) dt = self.get_datetime('start') if dt is not None and self.end_time is None: self.set_datetime('end', dt + et.default_duration) else: dt = self.get_datetime('end') if dt is not None and self.start_time is None: self.set_datetime('start', dt - et.default_duration) if et and et.max_days == 1: # avoid "Abandoning with 297 unsaved instances" when migrating data # that was created before the current rules self.end_date = None msg = self.duration_veto() if msg is not None: raise ValidationError(str(msg)) def start_time_changed(self, ar): et = self.event_type start_time = self.get_datetime('start') if start_time is not None \ and et is not None and et.default_duration is not None: dt = start_time + et.default_duration self.set_datetime('end', dt) # self.end_time = str(self.start_time + et.default_duration) # removed because this behaviour is irritating # def end_time_changed(self, ar): # et = self.event_type # end_time = self.get_datetime('end', 'start') # if end_time is not None \ # and et is not None and et.default_duration is not None: # dt = end_time - et.default_duration # self.set_datetime('start', dt) # # self.start_time = str(self.end_time - et.default_duration) def get_change_observers(self, ar=None): # implements ChangeNotifier if not self.is_user_modified(): return for x in super(Event, self).get_change_observers(ar): yield x for u in (self.user, self.assigned_to): if u is not None: yield (u, u.mail_mode) def has_conflicting_events(self): qs = self.get_conflicting_events() if qs is None: return False if self.event_type is not None: if self.event_type.transparent: return False # holidays (all room events) conflict also with events # whose type otherwise would allow conflicting events if qs.filter(event_type__all_rooms=True).count() > 0: return True n = self.event_type.max_conflicting - 1 else: n = 0 # date = self.start_date # if date.day == 9 and date.month == 3: # dd.logger.info("20171130 has_conflicting_events() %s", qs.query) return qs.count() > n def get_conflicting_events(self): if self.transparent: return # if self.event_type is not None and self.event_type.transparent: # return # return False # Event = dd.resolve_model('cal.Event') # ot = ContentType.objects.get_for_model(RecurrentEvent) qs = self.__class__.objects.filter(transparent=False) qs = qs.exclude(event_type__transparent=True) # if self.state.transparent: # # cancelled entries are basically transparent to all # # others. Except if they have an owner, in which case we # # wouldn't want Lino to put another automatic entry at # # that date. # if self.owner_id is None: # return # qs = qs.filter( # owner_id=self.owner_id, owner_type=self.owner_type) end_date = self.end_date or self.start_date flt = Q(start_date=self.start_date, end_date__isnull=True) flt |= Q(end_date__isnull=False, start_date__lte=self.start_date, end_date__gte=end_date) if end_date == self.start_date: if self.start_time and self.end_time: # the other starts before me and ends after i started c1 = Q(start_time__lte=self.start_time, end_time__gt=self.start_time) # the other ends after me and started before i ended c2 = Q(end_time__gte=self.end_time, start_time__lt=self.end_time) # the other is full day c3 = Q(end_time__isnull=True, start_time__isnull=True) flt &= (c1 | c2 | c3) qs = qs.filter(flt) # saved events don't conflict with themselves: if self.id is not None: qs = qs.exclude(id=self.id) # automatic events never conflict with other generated events # of same owner. Rule needed for update_events. if self.auto_type: qs = qs.exclude( # auto_type=self.auto_type, auto_type__isnull=False, owner_id=self.owner_id, owner_type=self.owner_type) # transparent events (cancelled or omitted) usually don't # cause a conflict with other events (e.g. a holiday). But a # cancelled course lesson should not tolerate another lesson # of the same course on the same date. ntstates = EntryStates.filter(transparent=False) if self.owner_id is None: if self.state.transparent: return qs = qs.filter(state__in=ntstates) else: if self.state.transparent: qs = qs.filter(owner_id=self.owner_id, owner_type=self.owner_type) else: qs = qs.filter( Q(state__in=ntstates) | Q(owner_id=self.owner_id, owner_type=self.owner_type)) if self.room is None: # an entry that needs a room but doesn't yet have one, # conflicts with any all-room entry (e.g. a holiday). For # generated entries this list extends to roomed entries of # the same generator. if self.event_type is None or not self.event_type.all_rooms: if self.owner_id is None: qs = qs.filter(event_type__all_rooms=True) else: qs = qs.filter( Q(event_type__all_rooms=True) | Q(owner_id=self.owner_id, owner_type=self.owner_type) ) else: # other event in the same room c1 = Q(room=self.room) # other event locks all rooms (e.g. holidays) # c2 = Q(room__isnull=False, event_type__all_rooms=True) c2 = Q(event_type__all_rooms=True) qs = qs.filter(c1 | c2) if self.user is not None: if self.event_type is not None: if self.event_type.locks_user: # c1 = Q(event_type__locks_user=False) # c2 = Q(user=self.user) # qs = qs.filter(c1|c2) qs = qs.filter(user=self.user, event_type__locks_user=True) # qs = Event.objects.filter(flt,owner_type=ot) # if we.start_date.month == 7: # print 20131011, self, we.start_date, qs.count() # print 20131025, qs.query return qs def is_fixed_state(self): return self.state.fixed # return self.state in EntryStates.editable_states def is_user_modified(self): return self.state != EntryStates.suggested def before_ui_save(self, ar, **kw): # logger.info("20130528 before_ui_save") if self.state is EntryStates.suggested: self.state = EntryStates.draft super(Event, self).before_ui_save(ar, **kw) def on_create(self, ar): if self.event_type is None: self.event_type = ar.user.event_type or \ settings.SITE.site_config.default_event_type self.start_date = self.start_date or settings.SITE.today() self.start_time = timezone.now().time() # see also Assignable.on_create() super(Event, self).on_create(ar) if not settings.SITE.loading_from_dump: # print("20190328 before_ui_save", self.is_user_modified()) if isinstance(self.owner, RecurrenceSet): self.owner.before_auto_event_save(self) if isinstance(self.owner, EventGenerator): self.event_type = self.owner.update_cal_event_type() # def on_create(self,ar): # self.start_date = settings.SITE.today() # self.start_time = datetime.datetime.now().time() # ~ # default user is almost the same as for UserAuthored # ~ # but we take the *real* user, not the "working as" # if self.user_id is None: # u = ar.user # if u is not None: # self.user = u # super(Event,self).on_create(ar) def after_ui_save(self, ar, cw): super(Event, self).after_ui_save(ar, cw) self.update_guests.run_from_code(ar) def before_state_change(self, ar, old, new): super(Event, self).before_state_change(ar, old, new) if new.noauto: self.auto_type = None if new.guest_state and self.event_type_id and self.event_type.force_guest_states: for obj in self.guest_set.exclude(state=new.guest_state): obj.state = new.guest_state obj.full_clean() obj.save() def can_edit_guests_manually(self): if self.state.fill_guests: if self.event_type and self.event_type.fill_presences: return False return True def suggest_guests(self): done = set() for o in (self.owner, self.project): if isinstance(o, EventGenerator): if o in done: continue done.add(o) for obj in o.suggest_cal_guests(self): yield obj # if self.owner: # for obj in self.owner.suggest_cal_guests(self): # yield obj def get_event_summary(event, ar): # from django.utils.translation import ugettext as _ s = event.summary # if event.owner_id: # s += " ({0})".format(event.owner) if event.user is not None and event.user != ar.get_user(): if event.access_class == AccessClasses.show_busy: s = _("Busy") s = event.user.username + ': ' + str(s) elif settings.SITE.project_model is not None \ and event.project is not None: s += " " + str(_("with")) + " " + str(event.project) if event.state: s = ("(%s) " % str(event.state)) + s n = event.guest_set.all().count() if n: s = ("[%d] " % n) + s return s def get_postable_recipients(self): """return or yield a list of Partners""" if self.project: if isinstance(self.project, dd.plugins.cal.partner_model): yield self.project for g in self.guest_set.all(): yield g.partner # if self.user.partner: # yield self.user.partner def get_mailable_type(self): return self.event_type def get_mailable_recipients(self): if self.project: if isinstance(self.project, dd.plugins.cal.partner_model): yield ('to', self.project) for g in self.guest_set.all(): yield ('to', g.partner) if self.user.partner: yield ('cc', self.user.partner) # def get_mailable_body(self,ar): # return self.description @dd.displayfield(_("When"), sortable_by=['start_date', 'start_time']) def when_text(self, ar): txt = when_text(self.start_date, self.start_time) if self.end_date and self.end_date != self.start_date: txt += "-" + when_text(self.end_date, self.end_time) return txt @dd.displayfield(_("When"), sortable_by=['start_date', 'start_time']) def when_html(self, ar): if ar is None: return '' txt = when_text(self.start_date, self.start_time) if False: # removed 20181106 because it is irritating and # nobody uses it. return rt.models.cal.EntriesByDay.as_link(ar, self.start_date, txt) return self.obj2href(ar, txt) @dd.displayfield(_("Link URL")) def url(self, ar): return 'foo' @dd.virtualfield(dd.DisplayField(_("Reminder"))) def reminder(self, request): return False # reminder.return_type = dd.DisplayField(_("Reminder")) def get_calendar(self): # for sub in Subscription.objects.filter(user=ar.get_user()): # if sub.contains_event(self): # return sub return None @dd.virtualfield(dd.ForeignKey('cal.Calendar')) def calendar(self, ar): return self.get_calendar() def get_print_language(self): # if settings.SITE.project_model is not None and self.project: if self.project: return self.project.get_print_language() if self.user: return self.user.language return settings.SITE.get_default_language() @classmethod def get_default_table(cls): return OneEvent @classmethod def on_analyze(cls, lino): cls.DISABLED_AUTO_FIELDS = dd.fields_list(cls, "summary") super(Event, cls).on_analyze(lino) def auto_type_changed(self, ar): if self.auto_type: self.summary = self.owner.update_cal_summary( self.event_type, self.auto_type)
def warn_jnl_account(jnl): fld = jnl._meta.get_field('account') raise Warning(_("Field '{0}' in journal '{0}' is empty!").format( fld.verbose_name, jnl))
class Meta: app_label = 'cal' verbose_name = _("Recurrency policy") verbose_name_plural = _('Recurrency policies') abstract = dd.is_abstract_model(__name__, 'EventPolicy')
class ContactDetailsOwner(Contactable, Phonable): class Meta: abstract = True if dd.is_installed('phones'): def after_ui_save(self, ar, cw): if cw is None: # it's a new instance for cdt in ContactDetailTypes.get_list_items(): self.propagate_contact_detail(cdt) pass else: for k, old, new in cw.get_updates(): cdt = ContactDetailTypes.find(field_name=k) # cdt = getattr(ContactDetailTypes, k, False) if cdt: self.propagate_contact_detail(cdt) super(ContactDetailsOwner, self).after_ui_save(ar, cw) def propagate_contact_detail(self, cdt): k = cdt.field_name if k: value = getattr(self, k) ContactDetail = rt.models.phones.ContactDetail kw = dict(partner=self, primary=True, detail_type=cdt) try: cd = ContactDetail.objects.get(**kw) if value: cd.value = value # don't full_clean() because no need to check # primary of other items cd.save() else: cd.delete() except ContactDetail.DoesNotExist: if value: kw.update(value=value) cd = ContactDetail(**kw) # self.phones_by_partner.add(cd, bulk=False) cd.save() def propagate_contact_details(self, ar=None): watcher = ChangeWatcher(self) for cdt in ContactDetailTypes.get_list_items(): self.propagate_contact_detail(cdt) if ar is not None: watcher.send_update(ar) def get_overview_elems(self, ar): # elems = super(ContactDetailsOwner, self).get_overview_elems(ar) yield rt.models.phones.ContactDetailsByPartner.get_table_summary( self, ar) @dd.displayfield(_("Contact details")) def contact_details(self, ar): if ar is None: return '' sar = rt.models.phones.ContactDetailsByPartner.request(parent=ar, master_instance=self) items = [o.detail_type.as_html(o, sar) for o in sar if not o.end_date] return E.p(*join_elems(items, sep=', ')) else: def get_overview_elems(self, ar): return [] @dd.displayfield(_("Contact details")) def contact_details(self, ar): # if ar is None: # return '' items = [] for cdt in ContactDetailTypes.get_list_items(): if cdt.field_name: value = getattr(self, cdt.field_name) if value: items.append(cdt.format(value)) # items.append(ContactDetailTypes.email.format(self.email)) # # items.append(E.a(self.email, href="mailto:" + self.email)) # items.append(self.phone) # items.append(E.a(self.url, href=self.url)) return E.p(*join_elems(items, sep=', '))
class PaymentOrder(FinancialVoucher, Printable): class Meta: app_label = 'finan' abstract = dd.is_abstract_model(__name__, 'PaymentOrder') verbose_name = _("Payment Order") verbose_name_plural = _("Payment Orders") total = dd.PriceField(_("Total"), blank=True, null=True) execution_date = models.DateField( _("Execution date"), blank=True, null=True) # show_items = dd.ShowSlaveTable('finan.ItemsByPaymentOrder') write_xml = WritePaymentsInitiation() @dd.displayfield(_("Print")) def print_actions(self, ar): if ar is None: return '' elems = [] elems.append(ar.instance_action_button( self.write_xml)) return E.p(*join_elems(elems, sep=", ")) def get_wanted_movements(self): """Implements :meth:`lino_xl.lib.ledger.Voucher.get_wanted_movements` for payment orders. As a side effect this also computes the :attr:`total` field and saves the voucher. """ # dd.logger.info("20151211 cosi.PaymentOrder.get_wanted_movements()") acc = self.journal.account if not acc: warn_jnl_account(self.journal) # TODO: what if the needs_partner of the journal's account # is not checked? Shouldn't we raise an error here? amount, movements_and_items = self.get_finan_movements() if abs(amount > MAX_AMOUNT): dd.logger.warning("Oops, %s is too big", amount) return self.total = - amount item_partner = self.journal.partner is None for m, i in movements_and_items: yield m if item_partner: yield self.create_movement( i, (acc, None), m.project, not m.dc, m.amount, partner=m.partner, match=i.get_match()) if not item_partner: yield self.create_movement( None, (acc, None), None, not self.journal.dc, amount, partner=self.journal.partner, match=self) # 20191226 partner=self.journal.partner, match=self.get_default_match()) # side effect!: self.full_clean() self.save() def add_item_from_due(self, obj, **kwargs): # if obj.bank_account is None: # return i = super(PaymentOrder, self).add_item_from_due(obj, **kwargs) i.bank_account = obj.bank_account return i
class Meta: app_label = 'finan' verbose_name = _("Bank Statement item") verbose_name_plural = _("Bank Statement items")
class Voucher(UserAuthored, Duplicable, UploadController, PeriodRangeObservable): # class Voucher(UserAuthored, Duplicable, Registrable, UploadController, PeriodRangeObservable): class Meta: # abstract = True verbose_name = _("Voucher") verbose_name_plural = _("Vouchers") app_label = 'ledger' manager_roles_required = dd.login_required(VoucherSupervisor) # workflow_state_field = 'state' journal = JournalRef() entry_date = models.DateField(_("Entry date")) voucher_date = models.DateField(_("Voucher date")) accounting_period = dd.ForeignKey('ledger.AccountingPeriod', blank=True) number = VoucherNumber(_("No."), blank=True, null=True) narration = models.CharField(_("Narration"), max_length=200, blank=True) # state = VoucherStates.field(default='draft') def unused_get_partner(self): # return None raise NotImplementedError("{} must define get_partner()".format( self.__class__)) def unused_get_wanted_movements(self): # deactivated this because MRO is complex, see 20200128 # return [] raise NotImplementedError( "{} must define get_wanted_movements()".format(self.__class__)) @property def currency(self): """This is currently used only in some print templates. """ return dd.plugins.ledger.currency_symbol @dd.displayfield(_("No.")) def number_with_year(self, ar): return "{0}/{1}".format(self.number, self.accounting_period.year) @classmethod def quick_search_filter(model, search_text, prefix=''): """Overrides :meth:`lino.core.model.Model.quick_search_filter`. Examples: 123 -> voucher number 123 in current year 123/2014 -> voucher number 123 in 2014 """ # logger.info( # "20160612 Voucher.quick_search_filter(%s, %r, %r)", # model, search_text, prefix) parts = search_text.split('/') if len(parts) == 2: kw = { prefix + 'number': parts[0], prefix + 'accounting_period__year': parts[1] } return models.Q(**kw) if search_text.isdigit() and not search_text.startswith('0'): kw = { prefix + 'number': int(search_text), prefix + 'accounting_period__year': FiscalYear.get_or_create_from_date(dd.today()) } return models.Q(**kw) return super(Voucher, model).quick_search_filter(search_text, prefix) def __str__(self): if self.number is None: return "{0}#{1}".format(self.journal.ref, self.id) # moved to implementing subclasses: # if self.state not in dd.plugins.ledger.registered_states: # # raise Exception("20191223 {} is not in {}".format(self.state, dd.plugins.ledger.registered_states)) assert self.number is not None if self.journal.yearly_numbering: return "{0} {1}/{2}".format(self.journal.ref, self.number, self.accounting_period.year) return "{0} {1}".format(self.journal.ref, self.number) # if self.journal.ref: # return "%s %s" % (self.journal.ref,self.number) # return "#%s (%s %s)" % (self.number,self.journal,self.year) def full_clean(self, *args, **kwargs): if self.entry_date is None: self.entry_date = dd.today() if self.voucher_date is None: self.voucher_date = self.entry_date if not self.accounting_period_id: self.accounting_period = AccountingPeriod.get_default_for_date( self.entry_date) if self.number is None: self.number = self.journal.get_next_number(self) super(Voucher, self).full_clean(*args, **kwargs) def on_create(self, ar): super(Voucher, self).on_create(ar) if self.entry_date is None: if ar is None: self.entry_date = dd.today() else: info = LedgerInfo.get_for_user(ar.get_user()) self.entry_date = info.entry_date or dd.today() def on_duplicate(self, ar, master): self.number = self.entry_date = self.accounting_period = None self.on_create(ar) super(Voucher, self).on_duplicate(ar, master) def entry_date_changed(self, ar): self.accounting_period = AccountingPeriod.get_default_for_date( self.entry_date) self.voucher_date = self.entry_date self.accounting_period_changed(ar) info = LedgerInfo.get_for_user(ar.get_user()) info.entry_date = self.entry_date info.full_clean() info.save() def accounting_period_changed(self, ar): self.number = self.journal.get_next_number(self) def get_detail_action(self, ar): """Custom :meth:`get_detail_action <lino.core.model.Model.get_detail_action>` because the detail_layout to use depends on the journal's voucher type. """ if self.journal_id: table = self.journal.voucher_type.table_class if table: ba = table.detail_action ba = ba.action.defining_actor.detail_action # if ar is None or ba.get_row_permission(ar, self, None): # return ba if ar is None or ba.get_view_permission( ar.get_user().user_type): return ba return None return super(Voucher, self).get_detail_action(ar) def get_due_date(self): return self.entry_date def get_trade_type(self): return self.journal.trade_type def get_printed_name(self): return dd.babelattr(self.journal, 'printed_name') def get_movement_description(self, mvt, ar=None): if ar is None: return if self.narration: yield self.narration p = self.get_partner() if p is not None and p != ar.master_instance: yield ar.obj2html(p) if mvt.partner and mvt.partner != p: yield ar.obj2html(mvt.partner) def after_ui_save(self, ar, cw): super(Voucher, self).after_ui_save(ar, cw) p = self.get_partner() if p is None: return tt = self.get_trade_type() account = tt.get_partner_invoice_account(p) if account is None: return if self.items.exists(): return i = self.add_voucher_item(account=account) i.full_clean() i.save() @classmethod def get_journals(cls): vt = VoucherTypes.get_for_model(cls) return Journal.objects.filter(voucher_type=vt).order_by('seqno') @dd.chooser() def unused_accounting_period_choices(cls, entry_date): # deactivated because it also limits the choices of the # parameter field (which is a Lino bug) return rt.models.ledger.AccountingPeriod.get_available_periods( entry_date) @dd.chooser() def journal_choices(cls): # logger.info("20140603 journal_choices %r", cls) return cls.get_journals() @classmethod def create_journal(cls, trade_type=None, account=None, **kw): vt = VoucherTypes.get_for_model(cls) if isinstance(trade_type, str): trade_type = TradeTypes.get_by_name(trade_type) if isinstance(account, str): account = rt.models.ledger.Account.get_by_ref(account) if account is not None: kw.update(account=account) return Journal(trade_type=trade_type, voucher_type=vt, **kw) # def get_default_match(self): removed 20191226 # return str(self) # return "%s#%s" % (self.journal.ref, self.id) # return "%s%s" % (self.id, self.journal.ref) # def get_voucher_match(self): # return str(self) # "{0}{1}".format(self.journal.ref, self.number) def do_and_clear(self, func, do_clear): existing_mvts = self.movement_set.all() partners = set() # accounts = set() if not self.journal.auto_check_clearings: do_clear = False if do_clear: for m in existing_mvts.filter(account__clearable=True, partner__isnull=False): partners.add(m.partner) existing_mvts.delete() func(partners) if do_clear: for p in partners: check_clearings_by_partner(p) # for a in accounts: # check_clearings_by_account(a) # dd.logger.info("20151211 Done cosi.Voucher.register_voucher()") def disable_delete(self, ar=None): msg = self.journal.disable_voucher_delete(self) if msg is not None: return msg return super(Voucher, self).disable_delete(ar) def create_movement(self, item, acc_tuple, project, dc, amount, **kw): # dd.logger.info("20151211 ledger.create_movement()") account, ana_account = acc_tuple if account is None and item is not None: raise Warning("No account specified for {}".format(item)) if not isinstance(account, rt.models.ledger.Account): raise Warning("{} is not an Account object".format(account)) kw['voucher'] = self kw['account'] = account if ana_account is not None: kw['ana_account'] = ana_account kw['value_date'] = self.entry_date if account.clearable: kw.update(cleared=False) else: kw.update(cleared=True) if dd.plugins.ledger.project_model: kw['project'] = project if amount < 0: amount = -amount dc = not dc kw['amount'] = amount kw['dc'] = dc b = rt.models.ledger.Movement(**kw) return b def get_mti_leaf(self): return mti.get_child(self, self.journal.voucher_type.model) # def obj2html(self, ar): def obj2href(self, ar): return ar.obj2html(self.get_mti_leaf()) #~ def add_voucher_item(self,account=None,**kw): #~ if account is not None: #~ if not isinstance(account,ledger.Account): #~ if isinstance(account, str): #~ account = self.journal.chart.get_account_by_ref(account) #~ kw['account'] = account def add_voucher_item(self, account=None, **kw): if account is not None: if isinstance(account, str): account = rt.models.ledger.Account.get_by_ref(account) kw['account'] = account kw.update(voucher=self) #~ logger.info("20131116 %s",self.items.model) return self.items.model(**kw) #~ return super(AccountInvoice,self).add_voucher_item(**kw) def get_bank_account(self): """Return the `sepa.Account` object to which this voucher is to be paid. This is needed by :class:`lino_xl.lib.ledger.utils.DueMovement`. """ return None # raise NotImplementedError() def get_uploads_volume(self): if self.journal_id: return self.journal.uploads_volume
class Meta: app_label = 'finan' verbose_name = _("Payment Order item") verbose_name_plural = _("Payment Order items")
class Meta: # abstract = True verbose_name = _("Voucher") verbose_name_plural = _("Vouchers") app_label = 'ledger'
class Journal(BabelNamed, Sequenced, Referrable, PrintableType): class Meta: app_label = 'ledger' verbose_name = _("Journal") verbose_name_plural = _("Journals") ref_max_length = 5 trade_type = TradeTypes.field(blank=True) voucher_type = VoucherTypes.field() journal_group = JournalGroups.field() auto_check_clearings = models.BooleanField(_("Check clearing"), default=True) auto_fill_suggestions = models.BooleanField(_("Fill suggestions"), default=True) force_sequence = models.BooleanField(_("Force chronological sequence"), default=False) preliminary = models.BooleanField(_("Preliminary"), default=False) account = dd.ForeignKey('ledger.Account', blank=True, null=True) partner = dd.ForeignKey('contacts.Company', blank=True, null=True) printed_name = dd.BabelCharField(_("Printed document designation"), max_length=100, blank=True) dc = DebitOrCreditField(_("Primary booking direction")) yearly_numbering = models.BooleanField(_("Yearly numbering"), default=True) must_declare = models.BooleanField(default=True) uploads_volume = dd.ForeignKey("uploads.Volume", blank=True, null=True) # invert_due_dc = models.BooleanField( # _("Invert booking direction"), # help_text=_("Whether to invert booking direction of due movement."), # default=True) def refuse_missing_partner(self): if self.account is None: return False return True def get_doc_model(self): """The model of vouchers in this Journal. """ # print self,DOCTYPE_CLASSES, self.doctype return self.voucher_type.model #~ return DOCTYPES[self.doctype][0] def get_doc_report(self): return self.voucher_type.table_class #~ return DOCTYPES[self.doctype][1] def get_voucher(self, year=None, number=None, **kw): cl = self.get_doc_model() kw.update(journal=self, accounting_period__year=year, number=number) return cl.objects.get(**kw) def create_voucher(self, **kw): """Create an instance of this Journal's voucher model (:meth:`get_doc_model`). """ cl = self.get_doc_model() kw.update(journal=self) try: doc = cl() # ~ doc = cl(**kw) # wouldn't work. See Django ticket #10808 #~ doc.journal = self for k, v in kw.items(): setattr(doc, k, v) #~ print 20120825, kw except TypeError: #~ print 20100804, cl raise doc.on_create(None) #~ doc.full_clean() #~ doc.save() return doc def get_allowed_accounts(self, **kw): if self.trade_type: return self.trade_type.get_allowed_accounts(**kw) # kw.update(chart=self.chart) return rt.models.ledger.Account.objects.filter(**kw) def get_next_number(self, voucher): # ~ self.save() # 20131005 why was this? cl = self.get_doc_model() flt = dict() if self.yearly_numbering: flt.update(accounting_period__year=voucher.accounting_period.year) d = cl.objects.filter(journal=self, **flt).aggregate(models.Max('number')) number = d['number__max'] #~ logger.info("20121206 get_next_number %r",number) if number is None: return 1 return number + 1 def __str__(self): # s = super(Journal, self).__str__() s = dd.babelattr(self, 'name') if self.ref: s += " (%s)" % self.ref #~ return '%s (%s)' % (d.BabelNamed.__unicode__(self),self.ref or self.id) return s #~ return self.ref +'%s (%s)' % mixins.BabelNamed.__unicode__(self) #~ return self.id +' (%s)' % mixins.BabelNamed.__unicode__(self) # def save(self, *args, **kw): # #~ self.before_save() # r = super(Journal, self).save(*args, **kw) # self.after_save() # return r def after_ui_save(self, ar, cw): super(Journal, self).after_ui_save(ar, cw) settings.SITE.kernel.must_build_site_cache() # def after_save(self): # pass # def full_clean(self, *args, **kw): if self.dc is None: if self.trade_type: self.dc = self.trade_type.dc # elif self.account: # self.dc = self.account.type.dc else: self.dc = DEBIT # cannot be NULL if not self.name: self.name = self.id #~ if not self.pos: #~ self.pos = self.__class__.objects.all().count() + 1 super(Journal, self).full_clean(*args, **kw) def disable_voucher_delete(self, doc): # print "pre_delete_voucher", doc.number, self.get_next_number() if self.force_sequence: if doc.number + 1 != self.get_next_number(doc): return _("%s is not the last voucher in journal" % str(doc)) def get_template_groups(self): """Here we override the class method by an instance method. This means that we must also override all other methods of Printable who call the *class* method. This is currently only :meth:`template_choices`. """ return [self.voucher_type.model.get_template_group()] @dd.chooser(simple_values=True) def template_choices(cls, build_method, voucher_type): # Overrides PrintableType.template_choices to not use the class # method `get_template_groups`. if not voucher_type: return [] #~ print 20131006, voucher_type template_groups = [voucher_type.model.get_template_group()] return cls.get_template_choices(build_method, template_groups) def insert_voucher_button(self, ar): table_class = self.get_doc_report() sar = table_class.insert_action.request_from(ar, master_instance=self) # print(20170217, sar) sar.known_values.update(journal=self) # sar.known_values.update(journal=self, user=ar.get_user()) txt = dd.babelattr(self, 'printed_name') # txt = self.voucher_type.model._meta.verbose_name_plural btn = sar.ar2button(None, _("New {}").format(txt), icon_name=None) # btn.set("style", "padding-left:10px") return btn
# always None. if journal: fkw = {journal.trade_type.name + '_allowed': True} return rt.models.ledger.Account.objects.filter(**fkw) print("20151221 journal is None") return [] for tt in TradeTypes.objects(): dd.inject_field( 'ledger.Account', tt.name + '_allowed', models.BooleanField( verbose_name=tt.text, default=False, help_text=format_lazy( _("Whether this account is available for {} transactions."), tt.text))) dd.inject_field( 'contacts.Partner', 'payment_term', dd.ForeignKey('ledger.PaymentTerm', blank=True, null=True, help_text=_("The default payment term for " "sales invoices to this customer."))) class VoucherChecker(Checker): verbose_name = _("Check integrity of ledger vouchers") messages = dict( missing=_("Missing movement {0}."),
class PaymentOrderDetail(JournalEntryDetail): general = dd.Panel(""" entry_date number:6 total execution_date workflow_buttons narration finan.ItemsByPaymentOrder """, label=_("General"))