Fixes on payments

This commit is contained in:
Marc Aymerich 2015-05-30 14:44:05 +00:00
parent 7c5eff9a90
commit a8cad48fed
29 changed files with 751 additions and 201 deletions

11
TODO.md
View File

@ -413,3 +413,14 @@ touch /tmp/somefile
# inherit registers from parent? # inherit registers from parent?
# Disable pagination on membership fees (allways one page) # Disable pagination on membership fees (allways one page)
# datetime metric storage granularity: otherwise innacurate detection of billed metric on order.billed_on
# Serializers.validation migration to DRF3: grep -r 'attrs, source' *|grep -v '~'
serailzer self.instance on create.
# generate Direct debit q19 on a protected path, or store it on the transaction.proc
# regenerate direct debit q19
# add transproc.method for regeneration
# TODO wrapp admin delete: delete proc undo processing on related transactions

View File

@ -14,9 +14,9 @@ from . import settings
class Account(auth.AbstractBaseUser): class Account(auth.AbstractBaseUser):
# Username max_length determined by LINUX system user lentgh: 32 # Username max_length determined by LINUX system user/group lentgh: 32
username = models.CharField(_("username"), max_length=32, unique=True, username = models.CharField(_("username"), max_length=32, unique=True,
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."), help_text=_("Required. 32 characters or fewer. Letters, digits and ./-/_ only."),
validators=[ validators=[
validators.RegexValidator(r'^[\w.-]+$', _("Enter a valid username."), 'invalid') validators.RegexValidator(r'^[\w.-]+$', _("Enter a valid username."), 'invalid')
]) ])
@ -102,7 +102,7 @@ class Account(auth.AbstractBaseUser):
extra_context.update(context) extra_context.update(context)
with translation.override(self.language): with translation.override(self.language):
send_email_template( send_email_template(
template, extra_context, ('marcay@pangea.org',), email_from=email_from, html=html, template, extra_context, email_to, email_from=email_from, html=html,
attachments=attachments) attachments=attachments)
def get_full_name(self): def get_full_name(self):

View File

@ -76,8 +76,8 @@ def close_bills(modeladmin, request, queryset):
if num == 1: if num == 1:
url = change_url(transactions[0]) url = change_url(transactions[0])
else: else:
url = reverse('admin:transactions_transaction_changelist') url = reverse('admin:payments_transaction_changelist')
url += 'id__in=%s' % ','.join(map(str, transactions)) url += 'id__in=%s' % ','.join([str(t.id) for t in transactions])
context = { context = {
'url': url, 'url': url,
'num': num, 'num': num,
@ -109,19 +109,15 @@ close_bills.url_name = 'close'
def send_bills(modeladmin, request, queryset): def send_bills(modeladmin, request, queryset):
num = 0
for bill in queryset: for bill in queryset:
if not validate_contact(request, bill): if not validate_contact(request, bill):
return return
num += 1 num = 0
if num == 1:
bill.send()
else:
# Batch email
queryset.send()
for bill in queryset: for bill in queryset:
bill.send()
modeladmin.log_change(request, bill, 'Sent') modeladmin.log_change(request, bill, 'Sent')
messages.success(request, ungetetx( num += 1
messages.success(request, ungettext(
_("One bill has been sent."), _("One bill has been sent."),
_("%i bills have been sent.") % num, _("%i bills have been sent.") % num,
num)) num))

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 09:23+0000\n" "POT-Creation-Date: 2015-05-29 09:39+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,61 +28,63 @@ msgstr "Vista"
#: actions.py:55 #: actions.py:55
msgid "Selected bills should be in open state" msgid "Selected bills should be in open state"
msgstr "" msgstr "Les factures seleccionades han d'estar en estat obert"
#: actions.py:73 #: actions.py:73
msgid "Selected bills have been closed" msgid "Selected bills have been closed"
msgstr "" msgstr "Les factures seleccionades han estat tancades"
#: actions.py:86 #: actions.py:86
#, python-format #, python-format
msgid "<a href=\"%(url)s\">One related transaction</a> has been created" msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
msgstr "" msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
#: actions.py:87 #: actions.py:87
#, python-format #, python-format
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created" msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
msgstr "" msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>"
#: actions.py:93 #: actions.py:93
msgid "Are you sure about closing the following bills?" msgid "Are you sure about closing the following bills?"
msgstr "" msgstr "Estàs a punt de tancar les següents factures, estàs segur?"
#: actions.py:94 #: actions.py:94
msgid "" msgid ""
"Once a bill is closed it can not be further modified.</p><p>Please select a " "Once a bill is closed it can not be further modified.</p><p>Please select a "
"payment source for the selected bills" "payment source for the selected bills"
msgstr "" msgstr ""
"Una vegada la factura estigui tancada no podrà ser modificada.</p><p>Si us "
"plau selecciona un mètode de pagament per les factures seleccionades"
#: actions.py:107 #: actions.py:107
msgid "Close" msgid "Close"
msgstr "" msgstr "Tanca"
#: actions.py:125 #: actions.py:125
msgid "One bill has been sent." msgid "One bill has been sent."
msgstr "" msgstr "S'ha creat una factura"
#: actions.py:126 #: actions.py:126
#, python-format #, python-format
msgid "%i bills have been sent." msgid "%i bills have been sent."
msgstr "" msgstr "S'han enviat %i factures."
#: actions.py:128 #: actions.py:128
msgid "Resend" msgid "Resend"
msgstr "" msgstr "Reenviat"
#: actions.py:189 #: actions.py:189
#, python-format #, python-format
msgid "%(norders)s orders and %(nlines)s lines undoed." msgid "%(norders)s orders and %(nlines)s lines undoed."
msgstr "" msgstr "%(norders)s ordres i %(nlines)s línies desfetes."
#: actions.py:208 #: actions.py:208
msgid "Lines moved" msgid "Lines moved"
msgstr "" msgstr "Línies mogudes"
#: admin.py:49 admin.py:93 admin.py:128 forms.py:11 #: admin.py:49 admin.py:93 admin.py:128 forms.py:11
msgid "Total" msgid "Total"
msgstr "" msgstr "Total"
#: admin.py:80 #: admin.py:80
msgid "Description" msgid "Description"
@ -90,52 +92,52 @@ msgstr "Descripció"
#: admin.py:88 #: admin.py:88
msgid "Subtotal" msgid "Subtotal"
msgstr "" msgstr "Subtotal"
#: admin.py:118 #: admin.py:118
msgid "Is open" msgid "Is open"
msgstr "" msgstr "És oberta"
#: admin.py:123 #: admin.py:123
msgid "Subline" msgid "Subline"
msgstr "" msgstr "Sublínia"
#: admin.py:157 #: admin.py:157
msgid "No bills selected." msgid "No bills selected."
msgstr "" msgstr "No hi ha factures seleccionades"
#: admin.py:164 #: admin.py:164
#, python-format #, python-format
msgid "Manage %s bill lines." msgid "Manage %s bill lines."
msgstr "" msgstr "Gestiona %s línies de factura."
#: admin.py:166 #: admin.py:166
msgid "Bill not in open state." msgid "Bill not in open state."
msgstr "" msgstr "La factura no està en estat obert"
#: admin.py:169 #: admin.py:169
msgid "Not all bills are in open state." msgid "Not all bills are in open state."
msgstr "" msgstr "No totes les factures estan en estat obert"
#: admin.py:170 #: admin.py:170
msgid "Manage bill lines of multiple bills." msgid "Manage bill lines of multiple bills."
msgstr "" msgstr "Gestiona línies de factura de multiples factures."
#: admin.py:190 #: admin.py:190
msgid "Raw" msgid "Raw"
msgstr "" msgstr "Raw"
#: admin.py:208 #: admin.py:208
msgid "Created" msgid "Created"
msgstr "" msgstr "Creada"
#: admin.py:213 #: admin.py:213
msgid "lines" msgid "lines"
msgstr "" msgstr "línies"
#: admin.py:218 templates/bills/microspective.html:118 #: admin.py:218 templates/bills/microspective.html:118
msgid "total" msgid "total"
msgstr "" msgstr "total"
#: admin.py:226 models.py:88 models.py:352 #: admin.py:226 models.py:88 models.py:352
msgid "type" msgid "type"
@ -167,11 +169,11 @@ msgstr "Rectificació de quota de soci"
#: filters.py:22 #: filters.py:22
msgid "Pro-forma" msgid "Pro-forma"
msgstr "" msgstr "Pro-forma"
#: filters.py:41 #: filters.py:41
msgid "positive price" msgid "positive price"
msgstr "" msgstr "preu positiu"
#: filters.py:46 filters.py:64 #: filters.py:46 filters.py:64
msgid "Yes" msgid "Yes"
@ -183,69 +185,71 @@ msgstr "No"
#: filters.py:59 #: filters.py:59
msgid "has bill contact" msgid "has bill contact"
msgstr "" msgstr "té contacte de facturació"
#: forms.py:9 #: forms.py:9
msgid "Number" msgid "Number"
msgstr "" msgstr "Número"
#: forms.py:10 #: forms.py:10
msgid "Account" msgid "Account"
msgstr "" msgstr "Compte"
#: forms.py:12 #: forms.py:12
msgid "Type" msgid "Type"
msgstr "" msgstr "Tipus"
#: forms.py:13 #: forms.py:13
msgid "Source" msgid "Source"
msgstr "" msgstr "Font"
#: helpers.py:10 #: helpers.py:10
msgid "" msgid ""
"{relation} account \"{account}\" does not have a declared invoice contact. " "{relation} account \"{account}\" does not have a declared invoice contact. "
"You should <a href=\"{url}#invoicecontact-group\">provide one</a>" "You should <a href=\"{url}#invoicecontact-group\">provide one</a>"
msgstr "" msgstr ""
"{relation} compte \"{account}\" no te un contacte de facturació. Hauries de "
"<a href=\"{url}#invoicecontact-group\">proporcionar un</a>"
#: helpers.py:17 #: helpers.py:17
msgid "Related" msgid "Related"
msgstr "" msgstr "Relacionat"
#: helpers.py:24 #: helpers.py:24
msgid "Main" msgid "Main"
msgstr "" msgstr "Principal"
#: models.py:23 models.py:86 #: models.py:23 models.py:86
msgid "account" msgid "account"
msgstr "" msgstr "compte"
#: models.py:25 #: models.py:25
msgid "name" msgid "name"
msgstr "" msgstr "nom"
#: models.py:26 #: models.py:26
msgid "Account full name will be used when left blank." msgid "Account full name will be used when left blank."
msgstr "" msgstr "S'emprarà el nom complet del compte quan es deixi en blanc."
#: models.py:27 #: models.py:27
msgid "address" msgid "address"
msgstr "" msgstr "adreça"
#: models.py:28 #: models.py:28
msgid "city" msgid "city"
msgstr "" msgstr "ciutat"
#: models.py:30 #: models.py:30
msgid "zip code" msgid "zip code"
msgstr "" msgstr "codi postal"
#: models.py:31 #: models.py:31
msgid "Enter a valid zipcode." msgid "Enter a valid zipcode."
msgstr "" msgstr "Introdueix un codi postal vàlid."
#: models.py:32 #: models.py:32
msgid "country" msgid "country"
msgstr "" msgstr "país"
#: models.py:35 #: models.py:35
msgid "VAT number" msgid "VAT number"
@ -253,51 +257,51 @@ msgstr "NIF"
#: models.py:67 #: models.py:67
msgid "Paid" msgid "Paid"
msgstr "" msgstr "Pagat"
#: models.py:68 #: models.py:68
msgid "Pending" msgid "Pending"
msgstr "" msgstr "Pendent"
#: models.py:69 #: models.py:69
msgid "Bad debt" msgid "Bad debt"
msgstr "" msgstr "Incobrable"
#: models.py:81 #: models.py:81
msgid "Amendment Fee" msgid "Amendment Fee"
msgstr "" msgstr "Rectificació de quota de soci"
#: models.py:82 #: models.py:82
msgid "Pro forma" msgid "Pro forma"
msgstr "" msgstr "Pro forma"
#: models.py:85 #: models.py:85
msgid "number" msgid "number"
msgstr "" msgstr "número"
#: models.py:89 #: models.py:89
msgid "created on" msgid "created on"
msgstr "" msgstr "creat el"
#: models.py:90 #: models.py:90
msgid "closed on" msgid "closed on"
msgstr "" msgstr "tancat el"
#: models.py:91 #: models.py:91
msgid "open" msgid "open"
msgstr "" msgstr "obert"
#: models.py:92 #: models.py:92
msgid "sent" msgid "sent"
msgstr "" msgstr "enviat"
#: models.py:93 #: models.py:93
msgid "due on" msgid "due on"
msgstr "" msgstr "es deu"
#: models.py:94 #: models.py:94
msgid "updated on" msgid "updated on"
msgstr "" msgstr "actualitzada el"
#: models.py:97 #: models.py:97
msgid "comments" msgid "comments"
@ -305,11 +309,11 @@ msgstr "comentaris"
#: models.py:98 #: models.py:98
msgid "HTML" msgid "HTML"
msgstr "" msgstr "HTML"
#: models.py:285 #: models.py:285
msgid "bill" msgid "bill"
msgstr "" msgstr "factura"
#: models.py:286 models.py:350 templates/bills/microspective.html:73 #: models.py:286 models.py:350 templates/bills/microspective.html:73
msgid "description" msgid "description"
@ -332,7 +336,7 @@ msgstr "quantitat"
#: models.py:290 templates/bills/microspective.html:77 #: models.py:290 templates/bills/microspective.html:77
#: templates/bills/microspective.html:111 #: templates/bills/microspective.html:111
msgid "subtotal" msgid "subtotal"
msgstr "" msgstr "subtotal"
#: models.py:291 #: models.py:291
msgid "tax" msgid "tax"
@ -340,67 +344,69 @@ msgstr "impostos"
#: models.py:292 #: models.py:292
msgid "start" msgid "start"
msgstr "" msgstr "iniciar"
#: models.py:293 #: models.py:293
msgid "end" msgid "end"
msgstr "" msgstr "finalitzar"
#: models.py:295 #: models.py:295
msgid "Informative link back to the order" msgid "Informative link back to the order"
msgstr "" msgstr "Enllaç informatiu de l'ordre"
#: models.py:296 #: models.py:296
msgid "order billed" msgid "order billed"
msgstr "" msgstr "ordre facturada"
#: models.py:297 #: models.py:297
msgid "order billed until" msgid "order billed until"
msgstr "" msgstr "ordre facturada fins a"
#: models.py:298 #: models.py:298
msgid "created" msgid "created"
msgstr "" msgstr "creada"
#: models.py:300 #: models.py:300
msgid "amended line" msgid "amended line"
msgstr "" msgstr "línia rectificada"
#: models.py:343 #: models.py:343
msgid "Volume" msgid "Volume"
msgstr "" msgstr "Volum"
#: models.py:344 #: models.py:344
msgid "Compensation" msgid "Compensation"
msgstr "" msgstr "Compensació"
#: models.py:345 #: models.py:345
msgid "Other" msgid "Other"
msgstr "" msgstr "Altre"
#: models.py:349 #: models.py:349
msgid "bill line" msgid "bill line"
msgstr "" msgstr "línia de factura"
#: templates/bills/microspective-fee.html:107 #: templates/bills/microspective-fee.html:107
msgid "Due date" msgid "Due date"
msgstr "" msgstr "Data de pagament"
#: templates/bills/microspective-fee.html:108 #: templates/bills/microspective-fee.html:108
#, python-format #, python-format
msgid "On %(bank_account)s" msgid "On %(bank_account)s"
msgstr "" msgstr "Al %(bank_account)s"
#: templates/bills/microspective-fee.html:114 #: templates/bills/microspective-fee.html:114
#, python-format #, python-format
msgid "From %(ini)s to %(end)s" msgid "From %(ini)s to %(end)s"
msgstr "" msgstr "De %(ini)s a %(end)s"
#: templates/bills/microspective-fee.html:121 #: templates/bills/microspective-fee.html:121
msgid "" msgid ""
"\n" "\n"
"<strong>With your membership</strong> you are supporting ...\n" "<strong>With your membership</strong> you are supporting ...\n"
msgstr "" msgstr ""
"\n"
"<strong>Amb la teva quota de soci</strong> estàs donant suport ...\n"
#: templates/bills/microspective.html:49 #: templates/bills/microspective.html:49
msgid "DUE DATE" msgid "DUE DATE"
@ -411,18 +417,17 @@ msgid "TOTAL"
msgstr "TOTAL" msgstr "TOTAL"
#: templates/bills/microspective.html:57 #: templates/bills/microspective.html:57
#, fuzzy, python-format #, python-format
#| msgid "%(bill_type|upper)s DATE "
msgid "%(bill_type)s DATE" msgid "%(bill_type)s DATE"
msgstr "DATA %(bill_type|upper)s" msgstr "DATA %(bill_type)s"
#: templates/bills/microspective.html:74 #: templates/bills/microspective.html:74
msgid "period" msgid "period"
msgstr "" msgstr "període"
#: templates/bills/microspective.html:75 #: templates/bills/microspective.html:75
msgid "hrs/qty" msgid "hrs/qty"
msgstr "hrs/quant" msgstr "hrs/qnt"
#: templates/bills/microspective.html:76 #: templates/bills/microspective.html:76
msgid "rate/price" msgid "rate/price"
@ -446,15 +451,6 @@ msgid "PAYMENT"
msgstr "PAGAMENT" msgstr "PAGAMENT"
#: templates/bills/microspective.html:140 #: templates/bills/microspective.html:140
#, fuzzy, python-format
#| msgid ""
#| "\n"
#| " You can pay our <i>%(type)s</i> by bank transfer. "
#| "<br>\n"
#| " Please make sure to state your name and the <i>"
#| "%(type)s</i> number.\n"
#| " Our bank account number is <br>\n"
#| " "
msgid "" msgid ""
"\n" "\n"
" You can pay our <i>%(type)s</i> by bank transfer.<br>\n" " You can pay our <i>%(type)s</i> by bank transfer.<br>\n"
@ -464,8 +460,8 @@ msgid ""
" " " "
msgstr "" msgstr ""
"\n" "\n"
"Pots pagar aquesta <i>%(type)s</i> per transferencia banacaria.<br>Inclou el " "Pots pagar aquesta <i>%(type)s</i> per transferència bancaria.<br>Inclou el "
"teu nom i el numero de <i>%(type)s</i>. El nostre compte bancari és" "teu nom i el número de <i>%(type)s</i>. El nostre compte bancari és"
#: templates/bills/microspective.html:149 #: templates/bills/microspective.html:149
msgid "QUESTIONS" msgid "QUESTIONS"
@ -481,3 +477,9 @@ msgid ""
" your message.\n" " your message.\n"
" " " "
msgstr "" msgstr ""
"\n"
" Si tens algun dubte o pregunta sobre la teva <i>%(type)s</i>, si "
"us plau\n"
" contacta amb nosaltres a %(email)s. Et respondrem el més "
"ràpidament possible.\n"
" "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 09:23+0000\n" "POT-Creation-Date: 2015-05-28 12:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -20,47 +20,49 @@ msgstr ""
#: actions.py:37 #: actions.py:37
msgid "Download" msgid "Download"
msgstr "" msgstr "Descarga"
#: actions.py:47 #: actions.py:47
msgid "View" msgid "View"
msgstr "" msgstr "Vista"
#: actions.py:55 #: actions.py:55
msgid "Selected bills should be in open state" msgid "Selected bills should be in open state"
msgstr "" msgstr "Las facturas seleccionadas están en estado abierto"
#: actions.py:73 #: actions.py:73
msgid "Selected bills have been closed" msgid "Selected bills have been closed"
msgstr "" msgstr "Las facturas seleccionadas han sido cerradas"
#: actions.py:86 #: actions.py:86
#, python-format #, python-format
msgid "<a href=\"%(url)s\">One related transaction</a> has been created" msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
msgstr "" msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
#: actions.py:87 #: actions.py:87
#, python-format #, python-format
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created" msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
msgstr "" msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>"
#: actions.py:93 #: actions.py:93
msgid "Are you sure about closing the following bills?" msgid "Are you sure about closing the following bills?"
msgstr "" msgstr "Estás a punto de cerrar las sigüientes facturas. ¿Estás seguro?"
#: actions.py:94 #: actions.py:94
msgid "" msgid ""
"Once a bill is closed it can not be further modified.</p><p>Please select a " "Once a bill is closed it can not be further modified.</p><p>Please select a "
"payment source for the selected bills" "payment source for the selected bills"
msgstr "" msgstr ""
"Una vez cerrada la factura ya no se podrá modificar.</p><p>Por favor "
"seleciona un metodo de pago para las facturas seleccionadas"
#: actions.py:107 #: actions.py:107
msgid "Close" msgid "Close"
msgstr "" msgstr "Cerrar"
#: actions.py:125 #: actions.py:125
msgid "One bill has been sent." msgid "One bill has been sent."
msgstr "" msgstr "Se ha enviado una factura"
#: actions.py:126 #: actions.py:126
#, python-format #, python-format
@ -143,7 +145,7 @@ msgstr ""
#: admin.py:243 #: admin.py:243
msgid "Payment" msgid "Payment"
msgstr "" msgstr "Pago"
#: filters.py:17 #: filters.py:17
msgid "All" msgid "All"
@ -151,19 +153,19 @@ msgstr ""
#: filters.py:18 models.py:78 #: filters.py:18 models.py:78
msgid "Invoice" msgid "Invoice"
msgstr "" msgstr "Factura"
#: filters.py:19 models.py:79 #: filters.py:19 models.py:79
msgid "Amendment invoice" msgid "Amendment invoice"
msgstr "" msgstr "Factura rectificative"
#: filters.py:20 models.py:80 #: filters.py:20 models.py:80
msgid "Fee" msgid "Fee"
msgstr "" msgstr "Quota de socio"
#: filters.py:21 #: filters.py:21
msgid "Amendment fee" msgid "Amendment fee"
msgstr "" msgstr "Quota rectificativa"
#: filters.py:22 #: filters.py:22
msgid "Pro-forma" msgid "Pro-forma"
@ -382,17 +384,17 @@ msgstr ""
#: templates/bills/microspective-fee.html:107 #: templates/bills/microspective-fee.html:107
msgid "Due date" msgid "Due date"
msgstr "" msgstr "Fecha de pago"
#: templates/bills/microspective-fee.html:108 #: templates/bills/microspective-fee.html:108
#, python-format #, python-format
msgid "On %(bank_account)s" msgid "On %(bank_account)s"
msgstr "" msgstr "En %(bank_account)s"
#: templates/bills/microspective-fee.html:114 #: templates/bills/microspective-fee.html:114
#, python-format #, python-format
msgid "From %(ini)s to %(end)s" msgid "From %(ini)s to %(end)s"
msgstr "" msgstr "Desde %(ini)s hasta %(end)s"
#: templates/bills/microspective-fee.html:121 #: templates/bills/microspective-fee.html:121
msgid "" msgid ""
@ -402,45 +404,45 @@ msgstr ""
#: templates/bills/microspective.html:49 #: templates/bills/microspective.html:49
msgid "DUE DATE" msgid "DUE DATE"
msgstr "" msgstr "VENCIMIENTO"
#: templates/bills/microspective.html:53 #: templates/bills/microspective.html:53
msgid "TOTAL" msgid "TOTAL"
msgstr "" msgstr "TOTAL"
#: templates/bills/microspective.html:57 #: templates/bills/microspective.html:57
#, python-format #, python-format
msgid "%(bill_type)s DATE" msgid "%(bill_type)s DATE"
msgstr "" msgstr "FECHA %(bill_type)s"
#: templates/bills/microspective.html:74 #: templates/bills/microspective.html:74
msgid "period" msgid "period"
msgstr "" msgstr "periodo"
#: templates/bills/microspective.html:75 #: templates/bills/microspective.html:75
msgid "hrs/qty" msgid "hrs/qty"
msgstr "" msgstr "hrs/cant"
#: templates/bills/microspective.html:76 #: templates/bills/microspective.html:76
msgid "rate/price" msgid "rate/price"
msgstr "" msgstr "tarifa/precio"
#: templates/bills/microspective.html:111 #: templates/bills/microspective.html:111
#: templates/bills/microspective.html:114 #: templates/bills/microspective.html:114
msgid "VAT" msgid "VAT"
msgstr "" msgstr "IVA"
#: templates/bills/microspective.html:114 #: templates/bills/microspective.html:114
msgid "taxes" msgid "taxes"
msgstr "" msgstr "impuestos"
#: templates/bills/microspective.html:130 #: templates/bills/microspective.html:130
msgid "COMMENTS" msgid "COMMENTS"
msgstr "" msgstr "COMENTARIOS"
#: templates/bills/microspective.html:136 #: templates/bills/microspective.html:136
msgid "PAYMENT" msgid "PAYMENT"
msgstr "" msgstr "PAGO"
#: templates/bills/microspective.html:140 #: templates/bills/microspective.html:140
#, python-format #, python-format
@ -452,6 +454,9 @@ msgid ""
" Our bank account number is <br>\n" " Our bank account number is <br>\n"
" " " "
msgstr "" msgstr ""
"\n"
"Puedes pagar esta <i>%(type)s</i> por transferencia bancaria.<br>Incloye tu "
"nombre y el número de <i>%(type)s</i>. Nuestra cuenta bancaria es"
#: templates/bills/microspective.html:149 #: templates/bills/microspective.html:149
msgid "QUESTIONS" msgid "QUESTIONS"
@ -467,3 +472,9 @@ msgid ""
" your message.\n" " your message.\n"
" " " "
msgstr "" msgstr ""
"\n"
" Si tienes alguna duda o pregunta sobre tu <i>%(type)s</i>, por "
"favor\n"
" contacta con nosotros en %(email)s. Te responderemos lo más "
"rapidamente posible.\n"
" "

View File

@ -142,7 +142,7 @@ class Bill(models.Model):
def get_type(self): def get_type(self):
return self.type or self.get_class_type() return self.type or self.get_class_type()
def set_number(self): def get_number(self):
cls = type(self) cls = type(self)
bill_type = self.get_type() bill_type = self.get_type()
if bill_type == self.BILL: if bill_type == self.BILL:
@ -162,7 +162,7 @@ class Bill(models.Model):
number_length = settings.BILLS_NUMBER_LENGTH number_length = settings.BILLS_NUMBER_LENGTH
zeros = (number_length - len(str(number))) * '0' zeros = (number_length - len(str(number))) * '0'
number = zeros + str(number) number = zeros + str(number)
self.number = '{prefix}{year}{number}'.format(prefix=prefix, year=year, number=number) return '{prefix}{year}{number}'.format(prefix=prefix, year=year, number=number)
def get_due_date(self, payment=None): def get_due_date(self, payment=None):
now = timezone.now() now = timezone.now()
@ -178,13 +178,14 @@ class Bill(models.Model):
if not self.due_on: if not self.due_on:
self.due_on = self.get_due_date(payment=payment) self.due_on = self.get_due_date(payment=payment)
self.total = self.get_total() self.total = self.get_total()
self.html = self.render(payment=payment)
transaction = None transaction = None
if self.get_type() != self.PROFORMA: if self.get_type() != self.PROFORMA:
transaction = self.transactions.create(bill=self, source=payment, amount=self.total) transaction = self.transactions.create(bill=self, source=payment, amount=self.total)
self.closed_on = timezone.now() self.closed_on = timezone.now()
self.is_open = False self.is_open = False
self.is_sent = False self.is_sent = False
self.number = self.get_number()
self.html = self.render(payment=payment)
self.save() self.save()
return transaction return transaction
@ -207,37 +208,37 @@ class Bill(models.Model):
self.save(update_fields=['is_sent']) self.save(update_fields=['is_sent'])
def render(self, payment=False, language=None): def render(self, payment=False, language=None):
if payment is False:
payment = self.account.paymentsources.get_default()
context = Context({
'bill': self,
'lines': self.lines.all().prefetch_related('sublines'),
'seller': self.seller,
'buyer': self.buyer,
'seller_info': {
'phone': settings.BILLS_SELLER_PHONE,
'website': settings.BILLS_SELLER_WEBSITE,
'email': settings.BILLS_SELLER_EMAIL,
'bank_account': settings.BILLS_SELLER_BANK_ACCOUNT,
},
'currency': settings.BILLS_CURRENCY,
'payment': payment and payment.get_bill_context(),
'default_due_date': self.get_due_date(payment=payment),
'now': timezone.now(),
})
template_name = 'BILLS_%s_TEMPLATE' % self.get_type()
template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE)
bill_template = loader.get_template(template)
with translation.override(language or self.account.language): with translation.override(language or self.account.language):
if payment is False:
payment = self.account.paymentsources.get_default()
context = Context({
'bill': self,
'lines': self.lines.all().prefetch_related('sublines'),
'seller': self.seller,
'buyer': self.buyer,
'seller_info': {
'phone': settings.BILLS_SELLER_PHONE,
'website': settings.BILLS_SELLER_WEBSITE,
'email': settings.BILLS_SELLER_EMAIL,
'bank_account': settings.BILLS_SELLER_BANK_ACCOUNT,
},
'currency': settings.BILLS_CURRENCY,
'payment': payment and payment.get_bill_context(),
'default_due_date': self.get_due_date(payment=payment),
'now': timezone.now(),
})
template_name = 'BILLS_%s_TEMPLATE' % self.get_type()
template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE)
bill_template = loader.get_template(template)
html = bill_template.render(context) html = bill_template.render(context)
html = html.replace('-pageskip-', '<pdf:nextpage />') html = html.replace('-pageskip-', '<pdf:nextpage />')
return html return html
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.type: if not self.type:
self.type = self.get_type() self.type = self.get_type()
if not self.number or (self.number.startswith('O') and not self.is_open): if not self.number:
self.set_number() self.number = self.get_number()
super(Bill, self).save(*args, **kwargs) super(Bill, self).save(*args, **kwargs)
def get_subtotals(self): def get_subtotals(self):

View File

@ -175,12 +175,12 @@ a:hover {
} }
#lines .column-description { #lines .column-description {
width: 40%; width: 39%;
text-align: left; text-align: left;
} }
#lines .column-period { #lines .column-period {
width: 22%; width: 23%;
} }
#lines .column-quantity { #lines .column-quantity {

View File

@ -135,7 +135,7 @@
<div id="payment"> <div id="payment">
<span class="title">{% trans "PAYMENT" %}</span> <span class="title">{% trans "PAYMENT" %}</span>
{% if payment.message %} {% if payment.message %}
{{ payment.message | safe }} {{ payment.message|safe }}
{% else %} {% else %}
{% blocktrans with type=bill.get_type_display.lower %} {% blocktrans with type=bill.get_type_display.lower %}
You can pay our <i>{{ type }}</i> by bank transfer.<br> You can pay our <i>{{ type }}</i> by bank transfer.<br>

View File

@ -12,11 +12,10 @@ class EmailBackend(BaseEmailBackend):
""" """
A wrapper that manages a queued SMTP system. A wrapper that manages a queued SMTP system.
""" """
messages = 0
def send_messages(self, email_messages): def send_messages(self, email_messages):
if not email_messages: if not email_messages:
return return
# Count messages per request
cache = get_request_cache() cache = get_request_cache()
key = 'mailer.sent_messages' key = 'mailer.sent_messages'
sent_messages = cache.get(key) or 0 sent_messages = cache.get(key) or 0
@ -24,7 +23,7 @@ class EmailBackend(BaseEmailBackend):
cache.set(key, sent_messages) cache.set(key, sent_messages)
is_bulk = len(email_messages) > 1 is_bulk = len(email_messages) > 1
if sent_messages > settings.MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD: if sent_messages > settings.MAILER_NON_QUEUED_PER_REQUEST_THRESHOLD:
is_bulk = True is_bulk = True
default_priority = Message.NORMAL if is_bulk else Message.CRITICAL default_priority = Message.NORMAL if is_bulk else Message.CRITICAL
num_sent = 0 num_sent = 0

View File

@ -14,6 +14,8 @@ from .models import Message
def send_message(message, num=0, connection=None, bulk=100): def send_message(message, num=0, connection=None, bulk=100):
if not message.pk:
message.save()
if num >= bulk: if num >= bulk:
connection.close() connection.close()
connection = None connection = None

View File

@ -50,7 +50,7 @@ class Message(models.Model):
def sent(self): def sent(self):
self.state = self.SENT self.state = self.SENT
self.save() self.save(update_fiields=('state',))
def log(self, error): def log(self, error):
result = SMTPLog.SUCCESS result = SMTPLog.SUCCESS

View File

@ -13,7 +13,7 @@ MAILER_MESSAGES_CLEANUP_DAYS = Setting('MAILER_MESSAGES_CLEANUP_DAYS',
) )
MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD = Setting('MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD', MAILER_NON_QUEUED_PER_REQUEST_THRESHOLD = Setting('MAILER_NON_QUEUED_PER_REQUEST_THRESHOLD',
2, 2,
help_text=_("Number of emails that will be sent directly before starting to queue them."), help_text=_("Number of emails that will be sent immediately before starting to queue them."),
) )

View File

@ -1,6 +1,7 @@
from functools import partial from functools import partial
from django.contrib import messages from django.contrib import messages
from django.contrib.admin import actions
from django.db import transaction from django.db import transaction
from django.shortcuts import render from django.shortcuts import render
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -18,23 +19,25 @@ from .models import Transaction
def process_transactions(modeladmin, request, queryset): def process_transactions(modeladmin, request, queryset):
processes = [] processes = []
if queryset.exclude(state=Transaction.WAITTING_PROCESSING).exists(): if queryset.exclude(state=Transaction.WAITTING_PROCESSING).exists():
msg = _("Selected transactions must be on '{state}' state") messages.error(request,
messages.error(request, msg.format(state=Transaction.WAITTING_PROCESSING)) _("Selected transactions must be on '{state}' state").format(
state=Transaction.WAITTING_PROCESSING)
)
return return
for method, transactions in queryset.group_by('source__method').items(): for method, transactions in queryset.group_by('source__method').items():
if method is not None: if method is not None:
method = PaymentMethod.get(method) method = PaymentMethod.get(method)
procs = method.process(transactions) procs = method.process(transactions)
processes += procs processes += procs
for trans in transactions: for transaction in transactions:
modeladmin.log_change(request, trans, _("Processed")) modeladmin.log_change(request, transaction, _("Processed"))
if not processes: if not processes:
return return
opts = modeladmin.model._meta opts = modeladmin.model._meta
num = len(queryset) num = len(queryset)
context = { context = {
'title': ungettext( 'title': ungettext(
_("Selected transaction has been processed."), _("One selected transaction has been processed."),
_("%s Selected transactions have been processed.") % num, _("%s Selected transactions have been processed.") % num,
num), num),
'content_message': ungettext( 'content_message': ungettext(
@ -54,9 +57,9 @@ def process_transactions(modeladmin, request, queryset):
@transaction.atomic @transaction.atomic
@action_with_confirmation() @action_with_confirmation()
def mark_as_executed(modeladmin, request, queryset): def mark_as_executed(modeladmin, request, queryset):
for trans in queryset: for transaction in queryset:
trans.mark_as_executed() transaction.mark_as_executed()
modeladmin.log_change(request, trans, _("Executed")) modeladmin.log_change(request, transaction, _("Executed"))
num = len(queryset) num = len(queryset)
msg = ungettext( msg = ungettext(
_("One selected transaction has been marked as executed."), _("One selected transaction has been marked as executed."),
@ -70,9 +73,9 @@ mark_as_executed.verbose_name = _("Mark as executed")
@transaction.atomic @transaction.atomic
@action_with_confirmation() @action_with_confirmation()
def mark_as_secured(modeladmin, request, queryset): def mark_as_secured(modeladmin, request, queryset):
for trans in queryset: for transaction in queryset:
trans.mark_as_secured() transaction.mark_as_secured()
modeladmin.log_change(request, trans, _("Secured")) modeladmin.log_change(request, transaction, _("Secured"))
num = len(queryset) num = len(queryset)
msg = ungettext( msg = ungettext(
_("One selected transaction has been marked as secured."), _("One selected transaction has been marked as secured."),
@ -86,9 +89,9 @@ mark_as_secured.verbose_name = _("Mark as secured")
@transaction.atomic @transaction.atomic
@action_with_confirmation() @action_with_confirmation()
def mark_as_rejected(modeladmin, request, queryset): def mark_as_rejected(modeladmin, request, queryset):
for trans in queryset: for transaction in queryset:
trans.mark_as_rejected() transaction.mark_as_rejected()
modeladmin.log_change(request, trans, _("Rejected")) modeladmin.log_change(request, transaction, _("Rejected"))
num = len(queryset) num = len(queryset)
msg = ungettext( msg = ungettext(
_("One selected transaction has been marked as rejected."), _("One selected transaction has been marked as rejected."),
@ -157,9 +160,9 @@ abort.verbose_name = _("Abort")
@transaction.atomic @transaction.atomic
@action_with_confirmation(extra_context=_format_commit) @action_with_confirmation(extra_context=_format_commit)
def commit(modeladmin, request, queryset): def commit(modeladmin, request, queryset):
for trans in queryset: for transaction in queryset:
trans.mark_as_rejected() transaction.mark_as_rejected()
modeladmin.log_change(request, trans, _("Rejected")) modeladmin.log_change(request, transaction, _("Rejected"))
num = len(queryset) num = len(queryset)
msg = ungettext( msg = ungettext(
_("One selected transaction has been marked as rejected."), _("One selected transaction has been marked as rejected."),
@ -168,3 +171,29 @@ def commit(modeladmin, request, queryset):
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
commit.url_name = 'commit' commit.url_name = 'commit'
commit.verbose_name = _("Commit") commit.verbose_name = _("Commit")
def delete_selected(modeladmin, request, queryset):
""" Has to have same name as admin.actions.delete_selected """
if not queryset:
messages.warning(request, "No transaction process selected.")
return
if queryset.exclude(transactions__state=Transaction.WAITTING_EXECUTION).exists():
messages.error(request, "Done nothing. Not all related transactions in waitting execution.")
return
# Store before deleting
related_transactions = []
for process in queryset:
related_transactions.extend(process.transactions.filter(state=Transaction.WAITTING_EXECUTION))
response = actions.delete_selected(modeladmin, request, queryset)
if response is None:
# Confirmation
num = 0
for transaction in related_transactions:
transaction.state = Transaction.WAITTING_PROCESSING
transaction.save(update_fields=('state',))
num += 1
modeladmin.log_change(request, transaction, _("Unprocessed"))
messages.success(request, "%i related transactions marked as waitting for processing." % num)
return response
delete_selected.short_description = actions.delete_selected.short_description

View File

@ -122,8 +122,8 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
fields = ('data', 'file_url', 'created_at') fields = ('data', 'file_url', 'created_at')
readonly_fields = ('data', 'file_url', 'display_transactions', 'created_at') readonly_fields = ('data', 'file_url', 'display_transactions', 'created_at')
inlines = [TransactionInline] inlines = [TransactionInline]
actions = (actions.mark_process_as_executed, actions.abort, actions.commit) change_view_actions = (actions.mark_process_as_executed, actions.abort, actions.commit)
change_view_actions = actions actions = change_view_actions + (actions.delete_selected,)
def file_url(self, process): def file_url(self, process):
if process.file: if process.file:
@ -138,7 +138,7 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
# Because of values_list this query doesn't benefit from prefetch_related # Because of values_list this query doesn't benefit from prefetch_related
tx_ids = process.transactions.values_list('id', flat=True) tx_ids = process.transactions.values_list('id', flat=True)
for tx_id in tx_ids: for tx_id in tx_ids:
ids.append('#%i' % tx_id) ids.append(str(tx_id))
counter += 1 counter += 1
if counter > 10: if counter > 10:
counter = 0 counter = 0

View File

@ -0,0 +1,250 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 16:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: actions.py:21
msgid "Selected transactions must be on '{state}' state"
msgstr ""
#: actions.py:30
msgid "Processed"
msgstr ""
#: actions.py:37
msgid "Selected transaction has been processed."
msgstr ""
#: actions.py:38
#, python-format
msgid "%s Selected transactions have been processed."
msgstr ""
#: actions.py:41
msgid ""
"The following transaction process has been generated, you may want to save "
"it on your computer now."
msgstr ""
#: actions.py:43
#, python-format
msgid ""
"The following %s transaction processes have been generated, you may want to "
"save it on your computer now."
msgstr ""
#: actions.py:46
msgid "Process"
msgstr ""
#: actions.py:59 actions.py:130 models.py:93 models.py:161
msgid "Executed"
msgstr ""
#: actions.py:62
msgid "One selected transaction has been marked as executed."
msgstr ""
#: actions.py:63
#, python-format
msgid "%s selected transactions have been marked as executed."
msgstr ""
#: actions.py:67 actions.py:138
msgid "Mark as executed"
msgstr ""
#: actions.py:75 models.py:94
msgid "Secured"
msgstr ""
#: actions.py:78
msgid "One selected transaction has been marked as secured."
msgstr ""
#: actions.py:79
#, python-format
msgid "%s selected transactions have been marked as secured."
msgstr ""
#: actions.py:83
msgid "Mark as secured"
msgstr ""
#: actions.py:91 actions.py:162 models.py:95
msgid "Rejected"
msgstr ""
#: actions.py:94 actions.py:165
msgid "One selected transaction has been marked as rejected."
msgstr ""
#: actions.py:95 actions.py:166
#, python-format
msgid "%s selected transactions have been marked as rejected."
msgstr ""
#: actions.py:99
msgid "Mark as rejected"
msgstr ""
#: actions.py:133
msgid "One selected process has been marked as executed."
msgstr ""
#: actions.py:134
#, python-format
msgid "%s selected processes have been marked as executed."
msgstr ""
#: actions.py:146 models.py:162
msgid "Aborted"
msgstr ""
#: actions.py:149
msgid "One selected process has been aborted."
msgstr ""
#: actions.py:150
#, python-format
msgid "%s selected processes have been aborted."
msgstr ""
#: actions.py:154
msgid "Abort"
msgstr ""
#: actions.py:170
msgid "Commit"
msgstr ""
#: admin.py:42
msgid "ID"
msgstr ""
#: admin.py:101
msgid "proc"
msgstr ""
#: admin.py:152
msgid "Transactions"
msgstr ""
#: methods/creditcard.py:11
msgid "Label"
msgstr ""
#: methods/creditcard.py:12
msgid "Use a name such as \"Jo's Visa\" to remember which card it is."
msgstr ""
#: methods/creditcard.py:30
msgid "Credit card"
msgstr ""
#: methods/sepadirectdebit.py:23 methods/sepadirectdebit.py:30
msgid "Name"
msgstr ""
#: methods/sepadirectdebit.py:39
msgid "SEPA Direct Debit"
msgstr ""
#: methods/sepadirectdebit.py:48
msgid ""
"This bill will be automatically charged to your bank account with IBAN "
"number<br><strong>%s</strong>."
msgstr ""
"Aquesta factura es cobrarà automaticament en el teu compte bancari amb IBAN "
"<br><strong>%s</strong>."
#: models.py:19
msgid "account"
msgstr ""
#: models.py:21
msgid "method"
msgstr ""
#: models.py:23 models.py:166
msgid "data"
msgstr ""
#: models.py:24
msgid "active"
msgstr ""
#: models.py:91
msgid "Waitting processing"
msgstr ""
#: models.py:92
msgid "Waitting execution"
msgstr ""
#: models.py:98
msgid "bill"
msgstr ""
#: models.py:101
msgid "source"
msgstr ""
#: models.py:103
msgid "process"
msgstr ""
#: models.py:104 models.py:168
msgid "state"
msgstr ""
#: models.py:106
msgid "amount"
msgstr ""
#: models.py:108 models.py:169
msgid "created"
msgstr ""
#: models.py:109
msgid "modified"
msgstr ""
#: models.py:124
msgid "New transactions can not be allocated for this bill."
msgstr ""
#: models.py:160
msgid "Created"
msgstr ""
#: models.py:163
msgid "Commited"
msgstr ""
#: models.py:167
msgid "file"
msgstr ""
#: models.py:170
msgid "updated"
msgstr ""
#: models.py:173
msgid "Transaction processes"
msgstr ""

View File

@ -0,0 +1,250 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 16:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: actions.py:21
msgid "Selected transactions must be on '{state}' state"
msgstr ""
#: actions.py:30
msgid "Processed"
msgstr ""
#: actions.py:37
msgid "Selected transaction has been processed."
msgstr ""
#: actions.py:38
#, python-format
msgid "%s Selected transactions have been processed."
msgstr ""
#: actions.py:41
msgid ""
"The following transaction process has been generated, you may want to save "
"it on your computer now."
msgstr ""
#: actions.py:43
#, python-format
msgid ""
"The following %s transaction processes have been generated, you may want to "
"save it on your computer now."
msgstr ""
#: actions.py:46
msgid "Process"
msgstr ""
#: actions.py:59 actions.py:130 models.py:93 models.py:161
msgid "Executed"
msgstr ""
#: actions.py:62
msgid "One selected transaction has been marked as executed."
msgstr ""
#: actions.py:63
#, python-format
msgid "%s selected transactions have been marked as executed."
msgstr ""
#: actions.py:67 actions.py:138
msgid "Mark as executed"
msgstr ""
#: actions.py:75 models.py:94
msgid "Secured"
msgstr ""
#: actions.py:78
msgid "One selected transaction has been marked as secured."
msgstr ""
#: actions.py:79
#, python-format
msgid "%s selected transactions have been marked as secured."
msgstr ""
#: actions.py:83
msgid "Mark as secured"
msgstr ""
#: actions.py:91 actions.py:162 models.py:95
msgid "Rejected"
msgstr ""
#: actions.py:94 actions.py:165
msgid "One selected transaction has been marked as rejected."
msgstr ""
#: actions.py:95 actions.py:166
#, python-format
msgid "%s selected transactions have been marked as rejected."
msgstr ""
#: actions.py:99
msgid "Mark as rejected"
msgstr ""
#: actions.py:133
msgid "One selected process has been marked as executed."
msgstr ""
#: actions.py:134
#, python-format
msgid "%s selected processes have been marked as executed."
msgstr ""
#: actions.py:146 models.py:162
msgid "Aborted"
msgstr ""
#: actions.py:149
msgid "One selected process has been aborted."
msgstr ""
#: actions.py:150
#, python-format
msgid "%s selected processes have been aborted."
msgstr ""
#: actions.py:154
msgid "Abort"
msgstr ""
#: actions.py:170
msgid "Commit"
msgstr ""
#: admin.py:42
msgid "ID"
msgstr ""
#: admin.py:101
msgid "proc"
msgstr ""
#: admin.py:152
msgid "Transactions"
msgstr ""
#: methods/creditcard.py:11
msgid "Label"
msgstr ""
#: methods/creditcard.py:12
msgid "Use a name such as \"Jo's Visa\" to remember which card it is."
msgstr ""
#: methods/creditcard.py:30
msgid "Credit card"
msgstr ""
#: methods/sepadirectdebit.py:23 methods/sepadirectdebit.py:30
msgid "Name"
msgstr ""
#: methods/sepadirectdebit.py:39
msgid "SEPA Direct Debit"
msgstr ""
#: methods/sepadirectdebit.py:48
msgid ""
"This bill will be automatically charged to your bank account with IBAN "
"number<br><strong>%s</strong>."
msgstr ""
"Esta factura se cobrará automaticamente en tu cuenta bancaria con IBAN "
"<br><strong>%s</strong>."
#: models.py:19
msgid "account"
msgstr ""
#: models.py:21
msgid "method"
msgstr ""
#: models.py:23 models.py:166
msgid "data"
msgstr ""
#: models.py:24
msgid "active"
msgstr ""
#: models.py:91
msgid "Waitting processing"
msgstr ""
#: models.py:92
msgid "Waitting execution"
msgstr ""
#: models.py:98
msgid "bill"
msgstr ""
#: models.py:101
msgid "source"
msgstr ""
#: models.py:103
msgid "process"
msgstr ""
#: models.py:104 models.py:168
msgid "state"
msgstr ""
#: models.py:106
msgid "amount"
msgstr ""
#: models.py:108 models.py:169
msgid "created"
msgstr ""
#: models.py:109
msgid "modified"
msgstr ""
#: models.py:124
msgid "New transactions can not be allocated for this bill."
msgstr ""
#: models.py:160
msgid "Created"
msgstr ""
#: models.py:163
msgid "Commited"
msgstr ""
#: models.py:167
msgid "file"
msgstr ""
#: models.py:170
msgid "updated"
msgstr ""
#: models.py:173
msgid "Transaction processes"
msgstr ""

View File

@ -45,7 +45,7 @@ class SEPADirectDebit(PaymentMethod):
due_delta = datetime.timedelta(days=5) due_delta = datetime.timedelta(days=5)
def get_bill_message(self): def get_bill_message(self):
return _("This bill will been automatically charged to your bank account " return _("This bill will be automatically charged to your bank account "
" with IBAN number<br><strong>%s</strong>.") % self.instance.number " with IBAN number<br><strong>%s</strong>.") % self.instance.number
@classmethod @classmethod
@ -211,7 +211,7 @@ class SEPADirectDebit(PaymentMethod):
), ),
E.DbtrAcct( # Debtor Account E.DbtrAcct( # Debtor Account
E.Id( E.Id(
E.IBAN(data['iban']) E.IBAN(data['iban'].replace(' ', ''))
), ),
), ),
) )
@ -246,7 +246,7 @@ class SEPADirectDebit(PaymentMethod):
), ),
E.CdtrAcct( # Creditor Account E.CdtrAcct( # Creditor Account
E.Id( E.Id(
E.IBAN(data['iban']) E.IBAN(data['iban'].replace(' ', ''))
), ),
), ),
) )
@ -279,7 +279,8 @@ class SEPADirectDebit(PaymentMethod):
xsd_path = os.path.join(path, xsd) xsd_path = os.path.join(path, xsd)
schema_doc = etree.parse(xsd_path) schema_doc = etree.parse(xsd_path)
schema = etree.XMLSchema(schema_doc) schema = etree.XMLSchema(schema_doc)
sepa = etree.parse(StringIO(etree.tostring(sepa))) sepa = StringIO(etree.tostring(sepa).decode('utf8'))
sepa = etree.parse(sepa)
schema.assertValid(sepa) schema.assertValid(sepa)
process.file = file_name process.file = file_name
process.save(update_fields=['file']) process.save(update_fields=['file'])

View File

@ -99,7 +99,7 @@ class Transaction(models.Model):
related_name='transactions') related_name='transactions')
source = models.ForeignKey(PaymentSource, null=True, blank=True, source = models.ForeignKey(PaymentSource, null=True, blank=True,
verbose_name=_("source"), related_name='transactions') verbose_name=_("source"), related_name='transactions')
process = models.ForeignKey('payments.TransactionProcess', null=True, process = models.ForeignKey('payments.TransactionProcess', null=True, on_delete=models.SET_NULL,
blank=True, verbose_name=_("process"), related_name='transactions') blank=True, verbose_name=_("process"), related_name='transactions')
state = models.CharField(_("state"), max_length=32, choices=STATES, state = models.CharField(_("state"), max_length=32, choices=STATES,
default=WAITTING_PROCESSING) default=WAITTING_PROCESSING)
@ -111,7 +111,7 @@ class Transaction(models.Model):
objects = TransactionQuerySet.as_manager() objects = TransactionQuerySet.as_manager()
def __str__(self): def __str__(self):
return "Transaction #{}".format(self.id) return "#%i" % self.id
@property @property
def account(self): def account(self):

View File

@ -6,7 +6,7 @@
<p>{{ content_message }}</p> <p>{{ content_message }}</p>
<ul> <ul>
{% for proc in processes %} {% for proc in processes %}
<li> <a href="{{ proc.id }}">Process #{{ proc.id }}</a> <li> <a href="{% url admin:payments_transactionprocess_change' proc.pk|admin_urlquote %}">Process #{{ proc.id }}</a>
{% if proc.file %} {% if proc.file %}
<ul><li>File: <a href="{{ proc.file.url }}">{{ proc.file }}</a></li></ul> <ul><li>File: <a href="{{ proc.file.url }}">{{ proc.file }}</a></li></ul>
{% endif %} {% endif %}

View File

@ -94,4 +94,3 @@ def delete_selected(modeladmin, request, queryset):
return return
return admin.actions.delete_selected(modeladmin, request, queryset) return admin.actions.delete_selected(modeladmin, request, queryset)
delete_selected.short_description = _("Delete selected %(verbose_name_plural)s") delete_selected.short_description = _("Delete selected %(verbose_name_plural)s")

View File

@ -41,8 +41,8 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
add_form = SystemUserCreationForm add_form = SystemUserCreationForm
form = SystemUserChangeForm form = SystemUserChangeForm
ordering = ('-id',) ordering = ('-id',)
actions = (delete_selected, set_permission, disable) change_view_actions = (set_permission, disable)
change_view_actions = actions actions = (delete_selected,) + change_view_actions
def display_main(self, user): def display_main(self, user):
return user.is_main return user.is_main

View File

@ -31,10 +31,10 @@ class SystemUser(models.Model):
""" """
System users System users
Username max_length determined by LINUX system user lentgh: 32 Username max_length determined by LINUX system user/group lentgh: 32
""" """
username = models.CharField(_("username"), max_length=32, unique=True, username = models.CharField(_("username"), max_length=32, unique=True,
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."), help_text=_("Required. 32 characters or fewer. Letters, digits and ./-/_ only."),
validators=[validators.validate_username]) validators=[validators.validate_username])
password = models.CharField(_("password"), max_length=128) password = models.CharField(_("password"), max_length=128)
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),

View File

@ -7,7 +7,6 @@ def html_to_pdf(html, pagination=False):
""" converts HTL to PDF using wkhtmltopdf """ """ converts HTL to PDF using wkhtmltopdf """
context = { context = {
'pagination': textwrap.dedent("""\ 'pagination': textwrap.dedent("""\
--footer-center "Page [page] of [topage]"\\
--footer-center "Page [page] of [topage]" \\ --footer-center "Page [page] of [topage]" \\
--footer-font-name sans \\ --footer-font-name sans \\
--footer-font-size 7 \\ --footer-font-size 7 \\
@ -20,6 +19,6 @@ def html_to_pdf(html, pagination=False):
--use-xserver \\ --use-xserver \\
%(pagination)s \\ %(pagination)s \\
--margin-bottom 22 \\ --margin-bottom 22 \\
--margin-top 20 - -\ --margin-top 20 - - \
""") % context """) % context
return run(cmd, stdin=html.encode('utf-8')).stdout return run(cmd, stdin=html.encode('utf-8')).stdout

View File

@ -15,8 +15,6 @@ def render_email_template(template, context):
""" """
if isinstance(context, dict): if isinstance(context, dict):
context = Context(context) context = Context(context)
if isinstance(to, str):
to = [to]
if not 'site' in context: if not 'site' in context:
from orchestra import settings from orchestra import settings
@ -32,6 +30,8 @@ def render_email_template(template, context):
def send_email_template(template, context, to, email_from=None, html=None, attachments=[]): def send_email_template(template, context, to, email_from=None, html=None, attachments=[]):
if isinstance(to, str):
to = [to]
subject, message = render_email_template(template, context) subject, message = render_email_template(template, context)
msg = EmailMultiAlternatives(subject, message, email_from, to, attachments=attachments) msg = EmailMultiAlternatives(subject, message, email_from, to, attachments=attachments)
if html: if html:

View File

@ -10,7 +10,7 @@ celery==3.1.16
kombu==3.0.23 kombu==3.0.23
billiard==3.3.0.18 billiard==3.3.0.18
Markdown==2.4 Markdown==2.4
djangorestframework==3.1.1 djangorestframework==3.1.2
ecdsa==0.11 ecdsa==0.11
Pygments==1.6 Pygments==1.6
django-filter==0.7 django-filter==0.7