From 1e63c59a8af120da718bd8919795408a682b3f5b Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Wed, 23 Aug 2023 12:17:32 +0200 Subject: [PATCH 1/7] fix: loading trix editor overwrite in production --- app/javascript/application.js | 2 +- config/importmap.rb | 1 + config/initializers/assets.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/javascript/application.js b/app/javascript/application.js index 1ba4a01a..141d3cae 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,4 +1,4 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "trix" import "@rails/actiontext" -import "./trix-editor-overrides" +import "trix-editor-overrides" diff --git a/config/importmap.rb b/config/importmap.rb index f882664b..d6d39932 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -2,3 +2,4 @@ pin "application", preload: true pin "trix" pin "@rails/actiontext", to: "actiontext.js" +pin "trix-editor-overrides" \ No newline at end of file diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index d507a355..d52cecaa 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -9,4 +9,4 @@ Rails.application.config.assets.version = '1.0' # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. -Rails.application.config.assets.precompile += %w[application_legacy.js jquery.min.js] +Rails.application.config.assets.precompile += %w[application_legacy.js jquery.min.js trix-editor-overrides.js] From caa32de30c8404537aaaacb338a8d0638bbc4613 Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Wed, 23 Aug 2023 12:47:58 +0200 Subject: [PATCH 2/7] fix: rubocop violation --- config/importmap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/importmap.rb b/config/importmap.rb index d6d39932..3ba2318b 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -2,4 +2,4 @@ pin "application", preload: true pin "trix" pin "@rails/actiontext", to: "actiontext.js" -pin "trix-editor-overrides" \ No newline at end of file +pin "trix-editor-overrides" From 91f27a0a4888542ec96838e5e656734578e1d67e Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Sun, 3 Sep 2023 22:13:59 +0200 Subject: [PATCH 3/7] chore: update chrowdin translations --- config/locales/de.yml | 26 +- config/locales/es.yml | 299 ++++++++++++++++++- config/locales/fr.yml | 12 +- config/locales/nl.yml | 108 ++++--- crowdin.yml | 2 + plugins/current_orders/config/locales/de.yml | 4 + plugins/current_orders/config/locales/es.yml | 5 + plugins/discourse/config/locales/es.yml | 1 + plugins/documents/config/locales/de.yml | 1 + plugins/documents/config/locales/es.yml | 9 + plugins/messages/config/locales/de.yml | 2 + plugins/messages/config/locales/es.yml | 30 +- plugins/wiki/config/locales/de.yml | 4 + plugins/wiki/config/locales/es.yml | 48 +++ 14 files changed, 497 insertions(+), 54 deletions(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index b7f77c5d..6a957ec2 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -448,6 +448,7 @@ de: error_denied_sign_in: als ein anderer Benutzer anmelden error_feature_disabled: Diese Funktion ist derzeit nicht aktiviert. error_members_only: Diese Aktion ist nur für Mitglieder der Gruppe erlaubt! + error_minimum_balance: Ihr Kontostand liegt leider unter dem Minimum von %{min}. error_token: Zugriff verweigert (ungültiger Token)! article_categories: create: @@ -697,6 +698,7 @@ de: applications: Apps foodcoop: Foodcoop language: Sprache + layout: Layout list: Liste messages: Nachrichten others: Sonstiges @@ -1132,6 +1134,7 @@ de: create: Einladung verschicken tasks: required_users: "Es fehlen %{count} Mitstreiterinnen!" + task_title: "%{name} (%{duration}h)" home: apple_bar: desc: 'Abgebildet ist das Verhältnis von erledigten Aufgaben zu dem Bestellvolumen Deiner Bestellgruppe im Vergleich zum Durchschnitt in der Foodcoop. Konkret: Pro %{amount} Bestellsumme solltest Du eine Aufgabe machen!' @@ -1231,6 +1234,7 @@ de: header: feedback: desc: Fehler gefunden? Vorschlag? Idee? Kritik? + title: Feedback help: Hilfe logout: Abmelden ordergroup: Meine Bestellgruppe @@ -1268,6 +1272,7 @@ de: feedback: header: "%{user} schrieb am %{date}:" subject: Feedback zur Foodsoft + from_via_foodsoft: "%{name} via Foodsoft" invite: subject: Einladung in die Foodcoop text: | @@ -1423,6 +1428,7 @@ de: stock: Lager suppliers: Lieferanten/Artikel title: Artikel + dashboard: Übersichtsseite finances: accounts: Konten verwalten balancing: Bestellungen abrechnen @@ -1430,6 +1436,7 @@ de: home: Übersicht invoices: Rechnungen title: Finanzen + foodcoop: Foodcoop members: Mitglieder ordergroups: Bestellgruppen orders: @@ -1480,6 +1487,7 @@ de: articles: Artikel delivery_day: Liefertag heading: Bestellung für %{name} + name: Name number: Nummer to_address: Versandaddresse finish: @@ -1579,6 +1587,7 @@ de: index: article_pdf: Artikel PDF group_pdf: Gruppen PDF + matrix_pdf: Matrix PDF title: Abholtage sessions: logged_in: Angemeldet! @@ -1589,6 +1598,7 @@ de: forgot_password: Passwort vergessen? login: Anmelden nojs: Achtung, Cookies und Javascript müssen aktiviert sein! %{link} bitte abschalten. + noscript: NoScript title: Foodsoft anmelden shared: articles: @@ -1617,8 +1627,13 @@ de: who_ordered: Wer hat bestellt? order_download_button: article_pdf: Artikel PDF + download_file: Datei herunterladen + fax_csv: Fax CSV + fax_pdf: Fax PDF fax_txt: Fax Text group_pdf: Gruppen PDF + matrix_pdf: Matrix PDF + title: Herunterladen task_list: accept_task: Aufgabe übernehmen done: Erledigt @@ -1675,10 +1690,11 @@ de: profile: language: de: Deutsch + en: Englisch es: Spanisch - tr: Türkisch fr: Französisch nl: Niederländisch + tr: Türkisch required: mark: "*" text: benötigt @@ -1835,6 +1851,7 @@ de: confirm_delete_single_from_group: Bist Du sicher, dass Du diese Aufgabe löschen möchtest (und in Bezug stehende wiederkehrende Aufgabe behalten möchtest)? delete_group: Aufgabe und folgende löschen edit_group: Wiederkehrende ändern + hours: "%{count}h" mark_done: Als erledigt markieren reject_task: Aufgabe ablehnen title: Aufgabe anzeigen @@ -1861,6 +1878,9 @@ de: delete: Löschen download: Herunterladen edit: Bearbeiten + marks: + close: "×" + success: move: Verschieben or_cancel: oder abbrechen please_wait: Bitte warten... @@ -1870,6 +1890,10 @@ de: show: Anzeigen views: pagination: + first: "«" + last: "»" + next: "›" + previous: "‹" truncate: "..." workgroups: edit: diff --git a/config/locales/es.yml b/config/locales/es.yml index 6cacb564..d722a872 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -35,9 +35,15 @@ es: unit_quantity: Cantidad de unidades bank_account: balance: Saldo + bank_gateway: Pasarela bancaria description: Descripción iban: IBAN name: Nombre + bank_gateway: + authorization: Cabecera-Autorización + name: Nombre + unattended_user: Usuario desatendido + url: Enlace bank_transaction: amount: Cantidad date: Fecha @@ -65,6 +71,7 @@ es: ordergroup: Grupo de pedido user: Ingresado por financial_transaction_class: + ignore_for_account_balance: Ignorar en el saldo de la cuenta name: Nombre financial_transaction_type: bank_account: Cuenta bancaria @@ -186,6 +193,11 @@ es: phone2: teléfono 2 shared_sync_method: Cómo sincronizar url: Web + supplier_category: + name: Nombre + description: Descripción + financial_transaction_class: Clase de transacciones financieras + bank_account: Cuenta bancaria task: created_by: Creado por created_on: Creado en @@ -243,6 +255,8 @@ es: models: article: Artículo article_category: Categoría + bank_account: Cuenta bancaria + bank_gateway: Pasarela bancaria bank_transaction: Transacción bancaria delivery: Entrega financial_transaction: Transacción financiara @@ -258,6 +272,7 @@ es: stock_article: Artículo de stock stock_taking: Toma de inventario supplier: Proveedor + supplier_category: Categoría del proveedor task: Tarea user: Usuario workgroup: Grupo de trabajo @@ -268,7 +283,7 @@ es: all_ordergroups: Todos los grupos de pedido all_users: Todos los usuarios all_workgroups: Todos los grupos de trabajo - created_at: creado + created_at: creado first_paragraph: Aquí puedes administrar grupos y usuarios de Foodsoft. groupname: nombre del grupo members: miembros @@ -281,6 +296,14 @@ es: title: Administración type: tipo username: nombre de usuario + bank_accounts: + form: + title_edit: Editar cuenta bancaria + title_new: Añadir nueva cuenta bancaria + bank_gateways: + form: + title_edit: Editar pasarela bancaria + title_new: Añadir nueva pasarela bancaria configs: list: key: Clave @@ -308,6 +331,26 @@ es: finances: index: bank_accounts: Cuentas bancarias + first_paragraph: Aquí puede administrar las clases de transacciones financieras y los tipos de transacciones financieras correspondientes. Cada transacción financiera tiene un tipo, que usted tiene que seleccionar en cada transacción, si usted creó más de un tipo. Las clases de transacciones financieras pueden utilizarse para agrupar los tipos de transacciones financieras y se mostrarán como columnas adicionales en el resumen de la cuenta, si se ha creado más de una. + new_bank_account: Añadir nueva cuenta bancaria + new_financial_transaction_class: Añadir nueva clase de transacción financiera + new_bank_gateway: Añadir nueva pasarela bancaria + title: Finanzas + transaction_types: Tipos de transacciones financieras + supplier_categories: Categorías de proveedores + new_supplier_category: Nueva categoría de proveedor + transaction_types: + name: Nombre + new_financial_transaction_type: Añadir nuevo tipo de transacción financiera + financial_transaction_classes: + form: + title_edit: Editar clase de transacción financiera + title_new: Añadir nueva clase de transacción financiera + financial_transaction_types: + form: + name_short_desc: El nombre corto es obligatorio para los tipos de transacciones financieras que deben ser asignados automáticamente en las transacciones bancarias. Si hay varias cuentas bancarias, se puede seleccionar la cuenta preferida para las transferencias bancarias. + title_edit: Editar tipo de transacción financiera + title_new: Añadir nuevo tipo de transacción financiera mail_delivery_status: destroy_all: notice: Se han borrado todos los problemas de email @@ -345,11 +388,15 @@ es: notice: El usuario/a ha sido borrado edit: title: Edita usuario/a + form: + create_ordergroup: Crear grupo de pedido con el mismo nombre y añadir usuario. + send_welcome_mail: Enviar un correo de bienvenida al usuario/a. index: first_paragraph: Aquí puedes %{url}, editar y borar usuarios. new_user: Crea nuevo usuario/a new_users: crea nuevo show_deleted: Muestra usuarios borrados + title: Usuario/a administrador new: title: Crea nuevo usuario restore: @@ -390,6 +437,10 @@ es: workgroups: members: miembros name: nombre + supplier_categories: + form: + title_new: Añadir categoría de proveedor + title_edit: Editar categoría de proveedor application: controller: error_authn: Es necesaria la autenticación! @@ -397,6 +448,7 @@ es: error_denied_sign_in: entra como otro usuario/a error_feature_disabled: Esta opción está actualmente deshabilitada error_members_only: Esta acción está sólo disponible para miembros de un grupo. + error_minimum_balance: Lo sentimos, el saldo de tu cuenta está por debajo del mínimo de %{min}. error_token: Acceso denegado (invalid token). article_categories: create: @@ -433,6 +485,7 @@ es: error_update: 'Ha ocurrido un error miebtras se actualizaba el artículo ''%{article}'': %{msg}' parse_upload: no_file: Elige un archivo para subir. + notice: "%{count} artículos fueron analizados con éxito." sync: notice: El catálogo está actualizado shared_alert: "%{supplier} no está conectado a una base de datos externa" @@ -462,6 +515,7 @@ es: not_found: No se han encontrado articulos index: change_supplier: Cambiar proveedor ... + download: Descargar artículos edit_all: Editar todos ext_db: import: Importar artículo @@ -513,18 +567,29 @@ es: status: Estado (x=saltar) file_label: Por favor elige un archivo compatible options: - convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar). + convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar). outlist_absent: Borrar artículos que no están en el archivo subido. sample: juices: Jugos nuts: Nueces organic: Orgánico + supplier_1: Nuttyfarm + supplier_2: Brownfields + supplier_3: Greenfields tomato_juice: Jugo de tomate walnuts: Nogal submit: Subir archivo text_1: 'Aquí puedes subir una hoja de cálculo para actualizar los artículos de %{supplier}. Se aceptan los formatos Excel (xls, xlsx) y OpenOffice (ods), al igual que archivos CSV (con columnas separadas por ";" con codificación UTF-8). Solo se importará la primera hoja y las columnas deben estar en el siguiente orden:' text_2: Las hileras que se muestran aquí son ejemplos. Cuando hay una "x" en la primera columna, el artículo se sacará de la lista. Esto te permite editar la hoja de cálculo y rápidamente sacar muchos artículos a la vez, por ejemplo cuando los artículos ya no están disponibles con el proveedor. La categoría se hará coincidir con tu lista de categorías de Foodsoft (tanto por nombre de categoría como nombre de importación). title: Subir artículos de %{supplier} + bank_account_connector: + confirm: Por favor, confirme el código %{code}. + fields: + email: E-Mail + pin: PIN + password: Contraseña + tan: TAN + username: Nombre de Usuario/a config: hints: applepear_url: Web donde se explica el sistema de manzanas y peras. @@ -546,7 +611,9 @@ es: order_schedule: boxfill: recurr: Programa cuándo la fase de llenado de cajas comienza por defecto. + time: Tiempo por defecto cuando comienza la fase de llenado de caja del pedido. ends: + recurr: Programa para la fecha predeterminada de cierre de pedidos. time: Fecha por defecto cuando se cierran los pedidos. initial: La agenda comienza en esta fecha. page_footer: Se muestra en cada página en la parte inferior. Dejar vacío para desactivar el pie de página por completo. @@ -566,12 +633,15 @@ es: use_boxfill: Cuando está activado, cerca del cierre de un pedido los miembros no podrán cambiar su pedido a menos que se incremente el valor pedido total. Esto ayudará a llenar las cajas que faltan. Igualmente deberás decidir una fecha de llenado de cajas para los pedidos. use_iban: Cuando esta opción está habilitada, el proveedor y el usuario pueden guardan también su número de cuenta bancaria internacional (IBAN). use_nick: Muestra y utiliza apodos en lugar de nombres reales. Cuando activas esto debes chequear que todos los usuarios tengan apodo. + use_self_service: Cuando está activado, los miembros pueden usar las funciones de balance seleccionadas por sí mismos. + webstats_tracking_code: Código de seguimiento para analíticas web (como Piwik o Google analytics). Dejar en blanco si no usa estas analíticas. keys: applepear_url: Enlace de ayuda para el sistema de puntos-manzana charge_members_manually: Cambia los miembros manualmente contact: city: Ciudad country: País + email: E-mail phone: Teléfono street: Calle zip_code: Código postal @@ -579,6 +649,12 @@ es: currency_unit: Moneda custom_css: CSS adicional default_locale: Idioma por defecto + default_role_article_meta: Artículos + default_role_finance: Finanzas + default_role_invoices: Facturas + default_role_orders: Pedidos + default_role_pickups: Días de recogida + default_role_suppliers: Proveedores disable_invite: Desactivar invitaciones disable_members_overview: Desactivar la lista de miembros email_from: Dirección de email de origen @@ -604,6 +680,7 @@ es: price_markup: Margen de la cooperativa stop_ordering_under: Puntos-manzana mínimos tasks_period_days: Periodo + tasks_upfront_days: Crear de antemano tax_default: IVA por defecto time_zone: Zona horaria tolerance_is_costly: La tolerancia es prioritaria @@ -615,8 +692,10 @@ es: use_boxfill: Fase de llenar las cajas use_iban: Usar IBAN use_nick: Usa apodos + use_self_service: Usar auto servicio webstats_tracking_code: Código de seguimiento tabs: + applications: Aplicaciones foodcoop: Cooperativa language: Idioma layout: Disposición @@ -624,6 +703,7 @@ es: messages: Mensajes others: Otro payment: Finanzas + security: Seguridad tasks: Tareas deliveries: add_stock_change: @@ -683,9 +763,11 @@ es: - Unidad - Precio/Unidad - Subtotal + total: Total order_matrix: filename: Pedido %{name}-%{date} - matrix para ordenar heading: Descripción del artículo (%{count}) + title: 'Matriz de ordenamiento de pedidos: %{name}, cerrada a las %{date}' errors: general: Ha ocurrido un problema. general_again: Ha ocurrido un problema. Por favor inténtalo de nuevo. @@ -702,6 +784,7 @@ es: notice: Tus comentarios fueron enviados con éxito. ¡Muchas gracias! new: first_paragraph: '¿Encontraste un error? ¿Tienes sugerencias, ideas o comentarios? Nos gustaría recibir tus comentarios.' + second_paragraph: Tenga en cuenta que el equipo de Foodsoft es el único responsable del mantenimiento del software. Para preguntas relacionadas con la organización de tu Foodcoop, por favor contacta a la persona de contacto apropiada. send: Enviar title: Enviar comentarios finance: @@ -709,6 +792,8 @@ es: close: alert: 'Ocurrió un error en la contabilidad: %{message}' notice: El pedido se ha cerrado con éxito, el balance de la cuenta ha sido actualizado. + close_all_direct_with_invoice: + notice: '%{count} pedidos han sido liquidados.' close_direct: alert: 'El pedido no se puede cerrar: %{message}' notice: El pedido ha sido cerrado @@ -717,17 +802,23 @@ es: first_paragraph: 'Cuando el pedido se cierre se actualizarán todas las cuentas del grupo.
Las cuentas serán cargadas así:' or_cancel: o vuelve a contabilidad title: Cierra el pedido + edit_note: + title: Editar nota de pedido edit_results_by_articles: add_article: Añadir artículo amount: Importe + edit_transport: Editar transporte gross: Bruto net: Neto + edit_transport: + title: Distribuir costes de transporte group_order_articles: add_group: Añadir grupo total: Costes totales total_fc: Suma (precio al grupo) units: Unidades index: + close_all_direct_with_invoice: Cerrar todo con factura title: Pedidos cerrados invoice: edit: Editar factura @@ -778,7 +869,10 @@ es: with_extra_charge: 'con cargo extra:' without_extra_charge: 'sin cargo extra:' bank_accounts: + assign_unlinked_transactions: + notice: '%{count} transacciones han sido asignadas' import: + notice: '%{count} nuevas transacciones han sido importadas' no_import_method: Para esta cuenta bancaria no se ha configurado ningún método de importación. submit: Importar title: Importar transacciones bancarias para %{name} @@ -807,12 +901,16 @@ es: notice: El enlace a la factura ha sido añadido. create: notice: Se ha creado un nuevo enlance financiero. + create_financial_transaction: + notice: La transacción financiera ha sido añadida. index_bank_transaction: title: Añadir transacción bancaria index_financial_transaction: title: Añadir transacción financiera index_invoice: title: Añadir factura + new_financial_transaction: + title: Añadir transacción financiera remove_bank_transaction: notice: Se ha eliminado el enlace a la transacción bancaria. remove_financial_transaction: @@ -820,7 +918,15 @@ es: remove_invoice: notice: El enlace a la factura ha sido eliminado. show: + add_bank_transaction: Añadir transacción bancaria + add_financial_transaction: Añadir transacción financiera + add_invoice: Añade factura + amount: Cantidad + date: Fecha + description: Descripción + new_financial_transaction: Nueva transacción financiera title: Enlace financiero %{number} + type: Tipo financial_transactions: controller: create: @@ -829,7 +935,11 @@ es: alert: 'Ha ocurrido un error: %{error}' error_note_required: Note se requiere! notice: Se han guardado todas las transacciones + destroy: + notice: La transacción ha sido eliminada. index: + balance: 'Saldo de la cuenta: %{balance}' + last_updated_at: "(última actualización hace %{when})" new_transaction: Crea nueva transacción title: Balance de cuentas para %{name} index_collection: @@ -837,16 +947,26 @@ es: title: Transacciones financieras new: paragraph: Aquí puedes poner o quitar dinero del grupo de pedido %{name}. + paragraph_foodcoop: Aquí puedes poner y quitar dinero para el foodcoop. title: Nueva transacción new_collection: add_all_ordergroups: Añade todos los grupos de pedido + add_all_ordergroups_custom_field: Añadir todos los pedidos de grupo con %{label} + create_financial_link: Crear un vínculo financiero común para las nuevas transacciones. + create_foodcoop_transaction: Crear una transacción con la suma inversa para el foodcoop (en el caso de "doble entrada de cuenta") new_ordergroup: Añade nuevo grupo de pedido save: Guarda transacción + set_balance: Ajuste el saldo del grupo de pedido a la cantidad introducida. sidebar: Aquí puedes actualizar más cuentas al mismo tiempo. Por ejemplo, todas las transferencias del grupo de pedido de un balance de cuenta. title: Actualizar más cuentas ordergroup: remove: Remover remove_group: Remover grupo + transactions: + confirm_revert: '¿Estás seguro de que quieres revertir %{name}? En este caso se creará una nueva transacción con una cantidad invertida y en combinación con la transacción original ocultada. Estas transacciones ocultas sólo son visibles a través de la opción ''Mostrar oculto'' y no son visibles para los usuarios normales en absoluto.' + revert_title: Revertir la transacción, que la ocultará a los usuarios normales. + transactions_search: + show_hidden: Mostrar transacciones ocultas index: amount_fc: Importe(FC) end: Fin @@ -863,6 +983,7 @@ es: attachment_hint: Sólo se permiten los formatos JPEG y PDF. index: action_new: Crea nueva factura + show_unpaid: Mostrar facturas no pagadas title: Facturas new: title: Crea nueva factura @@ -874,8 +995,10 @@ es: title: Facturas impagas ordergroups: index: + new_financial_link: Nuevo enlace financiero new_transaction: Añade nuevas transacciones show_all: Todas las transacciones + show_foodcoop: Transacciones de Foodcoop title: Maneja los grupos ordergroups: account_statement: Balance de cuenta @@ -889,6 +1012,8 @@ es: only_active: Sólo grupos activos only_active_desc: "(han hecho al menos un pedido en los últimos 3 meses)" title: Grupo de pedido + ordergroups: + break: "%{start} - %{end}" users: index: body: "

Desde aquí puedes escribir un mensaje a los miembros de tu cooperativa Foodcoop. Recuerda habilitar en %{profile_link} tus detalles de contacto para que sean visibles.

" @@ -987,10 +1112,12 @@ es: application: edit_user: Edita usuario nick_fallback: "(no tiene apodo)" + role_admin: Admin role_article_meta: Artículos role_finance: Finanzas role_invoices: Facturas role_orders: Pedidos + role_pickups: Días de recogida role_suppliers: Proveedores show_google_maps: Muéstralo en Google maps sort_by: Ordena por %{text} @@ -1000,12 +1127,14 @@ es: orders: old_price: Precio anterior option_choose: Elige proveedor/stock + option_stock: Existencias order_pdf: Crea PDF submit: invite: create: envía invitación tasks: required_users: "Aún se necesitan %{count} miembros!" + task_title: "%{name} (%{duration}h)" home: apple_bar: desc: 'Esto muestra la proporción de tareas completadas respecto al volumen de pedidos de tu grupo de pedido en comparación con el promedio en Foodcoop. En práctica: por cada %{amount} de pedidos totales, tú deberías hacer una tarea!' @@ -1014,8 +1143,9 @@ es: warning: Cuidado, si tienes menos de %{threshold} puntos-manzana no puedes hacer un pedido! changes_saved: Guarda los cambios. index: + due_date_format: "%A %d %B" my_ordergroup: - last_update: La última actualización fue hace %{when} + last_update: La última actualización fue hace %{when} title: Mi grupo de pedido transactions: title: Últimas transacciones @@ -1047,12 +1177,21 @@ es: title: Mi Perfil user: since: "(miembro para %{when})" + title: "%{user}" + reference_calculator: + transaction_types_headline: Propósito + placeholder: Por favor, introduzca primero las cantidades que desea transferir en cada campo, para ver la referencia que debe utilizar para esa transacción. + text0: Por favor transfiera + text1: con la referencia + text2: a la cuenta bancaria + title: Calculador de referencia start_nav: admin: Administración finances: accounts: Actualizar cuentas settle: Pedidos de la cuenta title: Finanzas + foodcoop: Foodcoop members: Miembros new_ordergroup: Nuevo grupo de pedido new_user: Nuevo miembro @@ -1081,20 +1220,30 @@ es: ordering: confirm_change: Las modificaciones sobre este pedido se perderán cuando cambies el pedido. ¿Quieres perder los cambios que has hecho y continuar? trix_editor: - file_size_alert: ¡El archivo adjunto es demasiado grande! El tamaño máximo es de 512Mb + file_size_alert: '¡El archivo adjunto es demasiado grande! El tamaño máximo es de 512Mb' layouts: email: + footer_1_separator: "--" + footer_2_foodsoft: 'Foodsoft: %{url}' + footer_3_homepage: 'Foodcoop: %{url}' footer_4_help: 'Ayuda: %{url}' help: 'Ayuda' + foodsoft: Foodsoft footer: revision: revisión %{revision} header: feedback: desc: '¿Encontrase algún error? ¿Sugerencias? ¿Ideas?' + title: Sugerencias help: Ayuda logout: Salir ordergroup: Mis grupos de pedido profile: Edita perfil + reference_calculator: Calculador de referencia + logo: "foodsoft" + lib: + render_pdf: + page: Página %{number} de %{count} login: accept_invitation: body: "

Has sido invitado a formar parte de %{foodcoop} como miembro del grupo %{group}.

Si quieres participar, es necesario que completes este formulario.

Tu información no será compartida con terceros bajo ninguna razón. Puedes decidir qué información personal será visible. 'Todos' hace referencia a todos los miembros de Foodcoop. Sólo los administradores tienen acceso a tu información.

" @@ -1107,7 +1256,7 @@ es: error_invite_invalid: Tu invitación no es válida. error_token_invalid: La sesión ha expirado o no es válida. Prueba de nuevo. reset_password: - notice: Si tu email está ya registrado aquí, recibirás un mensaje con un enlace para + notice: Si tu email está ya registrado aquí, recibirás un mensaje con un enlace para update_password: notice: Tu contraseña ha sido actualizada. Prueba a conectarte ahora. forgot_password: @@ -1119,9 +1268,13 @@ es: submit: Guardar la nueva contraseña title: Nueva contraseña mailer: + dateformat: "%d %b" feedback: header: "%{user} escribió %{date}:" + subject: Comentarios para Foodsoft + from_via_foodsoft: "%{name} vía Foodsoft" invite: + subject: Invitación al Foodcoop text: | Hola! @@ -1164,6 +1317,8 @@ es: Queridos miebros de %{ordergroup}, El pedido de "%{order}" ha sido cerrado por %{user} en %{when}. + text1: | + Puede ser posiblemente recogido en %{pickup}. text2: | Los siguientes artículos se han pedido para tu grupo de pedido: text3: |- @@ -1173,6 +1328,18 @@ es: Abrazos %{foodcoop}. + order_received: + subject: 'Envío de pedido registrado: %{name}' + text0: | + Estimado %{ordergroup}, + + el pedido de "%{order}" ha sido recibido. + abundant_articles: Recibido demasiado + scarce_articles: Recibido muy poco + article_details: | + o %{name}: + -- Solicitado: %{ordered} x %{unit} + -- Recibido: %{received} x %{unit} order_result_supplier: subject: Nuevo pedido para %{name} text: | @@ -1187,6 +1354,16 @@ es: %{foodcoop} reset_password: subject: Hay tareas que se deben hacer ya! + text: | + Hola %{user}, + + Has (o alguien más) solicitado una nueva contraseña. + Para elegir una nueva contraseña, siga este enlace: %{link} + Este enlace funciona sólo una vez y expira el %{expires}. + Si no quieres cambiar tu contraseña, simplemente ignora este mensaje. Tu contraseña no ha sido cambiada aún. + + + ¡Saludos, tu equipo de Foodsoft! upcoming_tasks: nextweek: 'Tareas para la semana que viene:' subject: Tareas que hay que hacer ya! @@ -1199,16 +1376,50 @@ es: Saludos de %{foodcoop}. + welcome: + subject: Bienvenido al Foodcoop + text0: | + Estimado %{user}, + + se ha creado una nueva cuenta Foodsoft para ti. + text1: | + Para elegir una nueva contraseña, siga este enlace: %{link} + Este enlace solo funciona una vez y caduca el %{expires}. + Siempre puedes usar "¿Olvidaste la contraseña?" para obtener un nuevo enlace. + + + Saludos de %{foodcoop}. + messages_mailer: + foodsoft_message: + footer: | + Respuesta: %{reply_url} + Ver mensaje en línea: %{msg_url} + Opciones de mensaje: %{profile_url} + footer_group: | + Enviado al grupo: %{group} model: delivery: each_stock_article_must_be_unique: Los artículos de stock no pueden ser listados más de una vez. + financial_transaction: + foodcoop_name: Foodcoop + financial_transaction_type: + no_delete_last: Debe existir al menos un tipo de transacción financiera. + group_order: + stock_ordergroup_name: Existencias (%{user}) + invoice: + invalid_mime: tiene un tipo de MIME inválido (%{mime}) membership: no_admin_delete: No te puedes salir de este grupo porque eres el último adimistrador/a. + order_article: + error_price: debe especificarse y tener un precio actual user: no_ordergroup: no hay célula + group_order_article: + order_closed: El pedido está cerrado y no se puede modificar navigation: admin: config: Configuración + finance: Finanzas home: Resumen mail_delivery_status: Problemas de email ordergroups: Grupos de pedido @@ -1217,12 +1428,14 @@ es: workgroups: grupos de trabajo articles: categories: Categorías + stock: Existencias suppliers: Proveedores/artículos title: Artículos dashboard: Escritorio finances: accounts: Administrar cuentas balancing: Pedidos de cuenta + bank_accounts: Cuentas bancarias home: Resumen invoices: Facturas title: Finanzas @@ -1233,6 +1446,7 @@ es: archive: Mis Pedidos manage: Gestionar pedidos ordering: Hacer pedido! + pickups: Días de recogida title: Pedidos tasks: Tareas workgroups: Grupos de trabajo @@ -1270,6 +1484,7 @@ es: field_unlocked_title: La distribución de este artículo entre los grupos de pedido se ha cambiado a mano. Cuando cambies las cantidades, esos cambios manuales se perderán. edit_amounts: no_articles_available: Ningún artículo para añadir. + set_all_to_zero: Poner todo a cero fax: amount: Cantidad articles: Artículos @@ -1303,7 +1518,9 @@ es: error_closed: El pedido ya estaba cerrado error_nosel: Debes seleccionar al menos un artículo. Quizás quieres borrar el pedido? error_starts_before_boxfill: tiene que ser después de la fecha de comienzo (o estar vacío) + error_starts_before_ends: debe ser después de la fecha de inicio (o permanecer vacío) notice_close: 'Pedido: %{name}, hasta %{ends}' + stock: Existencias warning_ordered: 'Cuidado: los artículos marcados en rojo ya han sido pedidos en este pedido abierto. Si los deseleccionas aquí, todos los pedidos actuales de estos artículos se borrarán. Para proceder, confirma abajo.' warning_ordered_stock: 'Cuidado: Los artículos marcados en rojo ya han sido pedidos en este pedido de stock. Si los deseleccionas aquí, todos los pedidos y compras de estos artículos se borrarán y no estarán en la contabilidad. Para proceder, confirma abajo.' new: @@ -1313,6 +1530,8 @@ es: consider_member_tolerance: considera la tolerancia notice: 'Pedido recibido: %{msg}' notice_none: Ningún nuevo artículo para recibir + paragraph: Si el pedido y el importe recibido son los mismos, los campos correspondientes pueden estar vacíos. Sigue siendo buena práctica entrar en todos los campos, ya que esto proporciona una forma fácil de verificar que todos los artículos han sido seleccionados. + rest_to_stock: restablecer a valores en existencias submit: recibe pedido surplus_options: 'Opciones de distribución:' title: Recibiendo %{order} @@ -1326,8 +1545,15 @@ es: comments: title: Comentarios comments_link: Comentarios + confirm_delete: '¿Estás seguro/a de que quieres borrar el pedido?' + confirm_end: |- + ¿Realmente desea cerrar el pedido %{order}? + No hay vuelta atrás. + confirm_send_to_supplier: El pedido ya ha sido enviado al proveedor el %{when}. ¿Realmente desea enviarlo de nuevo? create_invoice: Añade factura + description1_order: "%{state} pedido de %{supplier} abierto por %{who}" description1_period: + pickup: y puede ser recogido en %{pickup} starts: abierto desde %{starts} starts_ends: abierto desde %{starts} hasta %{ends} description2: "%{ordergroups} ha pedido %{article_count} artículos, con un valor total de %{net_sum} / %{gross_sum} (neto / bruto)." @@ -1353,14 +1579,30 @@ es: notice: Se actualizó el pedido. update_order_amounts: msg1: "%{count} artículos (%{units} units) actualizados" + msg2: "%{count} (%{units}) usando tolerancia" + msg4: "%{count} (%{units}) sobra" + pickups: + document: + empty_selection: Debe seleccionar al menos un pedido. + filename: Recogida para %{date} + invalid_document: Tipo de documento inválido + title: Recogida para %{date} + index: + article_pdf: Artículo PDF + group_pdf: Grupo PDF + matrix_pdf: Matrix PDF + title: Días de recogida sessions: logged_in: '¡Te has conectado!' + logged_out: '¡Te has desconectado!' login_invalid_email: Dirección de email o contraseña no válidas login_invalid_nick: Usuario o contraseña no válidos new: forgot_password: '¿Has olvidado la contraseña?' login: Entra nojs: Atención, las cookies y el javascript deben ser activados! Por favor desactiva %{link}. + noscript: NoScript + title: Inicio de sesión Foodsoft shared: articles: ordered: Pedidos @@ -1389,6 +1631,11 @@ es: order_download_button: article_pdf: Artículos PDF download_file: Descargar archivo + fax_csv: Fax CSV + fax_pdf: Fax PDF + fax_txt: Texto fax + group_pdf: Grupo PDF + matrix_pdf: Matrix PDF title: Descargar task_list: accept_task: Acepta la tarea @@ -1404,9 +1651,13 @@ es: workgroup_members: title: Membresías de grupo simple_form: + error_notification: + default_message: Se han encontrado errores. Por favor, compruebe el formulario. hints: article: unit: por ej. KG o 1L o 500g + article_category: + description: lista separada por comas de nombres de categorías reconocidos en la importación/sincronización order_article: units_to_order: Si cambias la cantidad total de unidades enviadas también tendrás que cambiar los valores individuales de grupo haciendo click en el nombre del artículo. No serán recalculados automáticamente, así que a los otros grupos de pedido se les podrían ser cobrar artículos que no llegarán! update_global_price: Actualizar el precio para futuros pedidos @@ -1426,6 +1677,7 @@ es: notify: negative_balance: Infórmame cuando mi grupo de pedido tenga un balance negativo. order_finished: Infórmame acerca del resultado de mi pedido (cuando se cierre). + order_received: Infórmame sobre los datos de entrega (después de recibir el pedido). upcoming_tasks: Recordarme las tareas incompletas. profile: email_is_public: El email es visible para otros miembros @@ -1435,6 +1687,7 @@ es: settings_group: messages: Mensajes privacy: Privacidad + 'no': 'No' options: settings: profile: @@ -1446,6 +1699,7 @@ es: nl: Neerlandés tr: Turco required: + mark: "*" text: requerido 'yes': 'Sí' stock_takings: @@ -1459,22 +1713,34 @@ es: new: amount: Cantidad create: crea + stock_articles: Artículos en existencias + temp_inventory: inventario temporal + text_deviations: Por favor, rellene todas las desviaciones excedentes del %{inv_link}. Para la reducción, utilice un número negativo. + text_need_articles: Tienes que %{create_link} un nuevo artículo de existencias antes de poder usarlo aquí. + title: Crear nuevo inventario show: amount: Cantidad article: Artículo + confirm_delete: '¿Realmente deseas eliminar el inventario?' date: Fecha note: Nota overview: Inventario supplier: Proveedor title: Muestra inventario unit: Unidad + stock_takings: + confirm_delete: '¿Estás seguro que quieres borrar esto?' + date: Fecha + note: Nota + update: + notice: Inventario actualizado. stockit: check: not_empty: "%{name} no se pudo borrar, el inventario no es cero." copy: title: Copia artículo de stock create: - notice: Se ha creado el nuevo producto en stock "%{name}" + notice: Se ha creado el nuevo producto en stock "%{name}" derive: title: Añade un artículo en stock desde plantilla destroy: @@ -1493,6 +1759,7 @@ es: show_stock_takings: Resumen del inventario stock_count: 'Número de artículos' stock_worth: 'Valor actual del stock:' + title: Existencias (%{article_count}) toggle_unavailable: Muestra/esconde los artículos no disponibles view_options: Ver opciones new: @@ -1500,6 +1767,7 @@ es: title: Añade mi nuevo artículo de stock show: change_quantity: Cambia + datetime: Hora new_quantity: Nueva cantidad reason: Razón stock_changes: Cambio de cantidades en stock @@ -1517,6 +1785,7 @@ es: action_new: Crea un nuevo proveedor/a articles: artículos (%{count}) confirm_del: Estas seguro de que quieres borrar al proveedor %{name}? + deliveries: entregas (%{count}) stock: en stock (%{count}) title: Proveedores new: @@ -1582,9 +1851,10 @@ es: accept_task: Aceptar tarea confirm_delete_group: Estás seguro/a de que quieres borrar esta tarea y todas las tareas subsecuentes? confirm_delete_single: Estás seguro/a de que quieres borrar esta tarea? - confirm_delete_single_from_group: Estás seguro/a de que quieres borrar esta tarea (y mantener las tareas recurrentes relacionadas)? + confirm_delete_single_from_group: Estás seguro/a de que quieres borrar esta tarea (y mantener las tareas recurrentes relacionadas)? delete_group: Borrar esta tarea y las subsecuentes edit_group: Edita recurrencia + hours: "%{count}h" mark_done: Marca tarea como hecha reject_task: Rechaza tarea title: Muestra tarea @@ -1605,19 +1875,34 @@ es: back: Volver cancel: Cancelar close: Cerrar + confirm_delete: '¿Realmente desea eliminar %{name}?' + confirm_restore: '¿Realmente desea restaurar %{name}?' copy: Copia delete: Eliminar download: Descarga edit: Editar + marks: + close: "×" + success: + move: Mover or_cancel: o cancelar please_wait: Espera... restore: Restaura save: Guardar search_placeholder: Busca ... show: Mostrar + views: + pagination: + first: "«" + last: "»" + next: "›" + previous: "‹" + truncate: "..." workgroups: edit: title: Edita grupo de trabajo + error_last_admin_group: El último grupo con derechos de administrador no debe ser eliminado + error_last_admin_role: El rol de administrador del último grupo con derechos de administrador no puede ser retirado index: title: Grupos de trabajo update: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index cd0971da..dd79dab3 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -14,7 +14,7 @@ fr: gross_price: Prix TTC manufacturer: Product-rice-eur name: Nom - order_number: Numéro + order_number: Numéro order_number_short: Numéro origin: Lieu de production price: Prix HT @@ -417,8 +417,8 @@ fr: street: Rue zip_code: Code postal currency_unit: Monnaie - name: Nom disable_members_overview: Désactiver la liste des membres + name: Nom distribution_strategy: Stratégie de distribution distribution_strategy_options: first_order_first_serve: Distribuez d'abord à ceux qui ont commandé en premier @@ -864,7 +864,7 @@ fr: error_invite_invalid: Ton invitation n'est pas ou plus valide. error_token_invalid: Ton jeton de connexion n'est pas ou plus valide, essaie de cliquer à nouveau sur le lien. reset_password: - notice: Tu vas maintenant recevoir un message contenant un lien qui te permettra de réinitialiser ton mot de passe. + notice: Tu vas maintenant recevoir un message contenant un lien qui te permettra de réinitialiser ton mot de passe. update_password: notice: Ton mot de passe a été mis à jour. Tu peux maintenant de connecter. forgot_password: @@ -1097,7 +1097,7 @@ fr: closed: décomptée finished: clôturée open: en cours - received: reçu + received: reçu update: notice: La commande a été mise à jour. update_order_amounts: @@ -1349,7 +1349,7 @@ fr: notice: La description du boulot a été mise à jour. notice_converted: Le boulot a été converti en boulot ordinaire (sans répétition). user: - more: Tu t'ennuies en ce moment? Il y aura sûrement du boulot pour toi %{tasks_link}. + more: Tu t'ennuies en ce moment? Il y aura sûrement du boulot pour toi %{tasks_link}. tasks_link: par là-bas title: Ton boulot title_accepted: Boulots acceptés @@ -1374,7 +1374,7 @@ fr: edit: title: Modifier l'équipe error_last_admin_group: Impossible de supprimer la dernière cellule avec privilèges administratrices. - error_last_admin_role: Les privilèges administratrices ne peuvent pas être retirés à la dernière cellule qui les possède. + error_last_admin_role: Les privilèges administratrices ne peuvent pas être retirés à la dernière cellule qui les possède. index: title: Équipes update: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index d972c088..1faaea62 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -35,13 +35,19 @@ nl: unit_quantity: Colligrootte bank_account: balance: Tegoed + bank_gateway: Bank gateway description: Omschrijving iban: IBAN name: Naam + bank_gateway: + authorization: Autorisatiekoptekst + name: Naam + unattended_user: Gebruiker zonder toezicht + url: URL bank_transaction: amount: Bedrag date: Datum - external_id: Extern ID + external_id: Externe ID financial_link: Financiële link iban: IBAN reference: Referentie @@ -51,26 +57,27 @@ nl: note: Notitie supplier: Leverancier document: - created_at: Upload op - created_by: Upload door - data: Data + created_at: Aangemaakt op + created_by: Aangemaakt door + data: Gegevens mime: MIME-type name: Naam financial_transaction: amount: Bedrag created_on: Datum - financial_transaction_class: Financiële transactie klasse - financial_transaction_type: Financiële transactie type + financial_transaction_class: Financiële-transactieklasse + financial_transaction_type: Financiële-transactietype note: Notitie ordergroup: Huishouden - user: Ingevuld door + user: Ingevoerd door financial_transaction_class: + ignore_for_account_balance: Negeren voor rekeningsaldo name: Naam financial_transaction_type: bank_account: Bankrekening name: Naam financial_transaction_class: Klasse financiële transactie - name_short: Verkorte naam + name_short: Korte naam group_order: ordergroup: Huishouden price: Totaal bestelling @@ -81,16 +88,16 @@ nl: received: Ontvangen result: Hoeveelheid tolerance: Tolerantie - total_price: Som + total_price: Totaal unit_price: Prijs/Eenheid invoice: amount: Bedrag attachment: Bijlage - created_at: Gemaakt op - created_by: Gemaakt door + created_at: Aangemaakt op + created_by: Aangemaakt door date: Factuurdatum delete_attachment: Bijlage verwijderen - deliveries: Voorraad levering + deliveries: Voorraadlevering deposit: Statiegeld in rekening gebracht deposit_credit: Statiegeld teruggekregen financial_link: Financiële link @@ -151,10 +158,10 @@ nl: contact_address: Adres contact_person: Contactpersoon contact_phone: Telefoon - description: Omschrijving + description: Beschrijving ignore_apple_restriction: Bestelstop vanwege appelpunten negeren last_order: Laatste bestelling - last_user_activity: Laatst actief + last_user_activity: Laatste activiteit name: Naam user_tokens: Leden stock_article: @@ -186,9 +193,14 @@ nl: phone2: Telefoon 2 shared_sync_method: Hoe synchroniseren url: Homepage + supplier_category: + name: Naam + description: Beschrijving + financial_transaction_class: Financiële-transactieklasse + bank_account: Bankrekening task: - created_by: Gemaakt door - created_on: Gemaakt op + created_by: Aangemaakt door + created_on: Aangemaakt op description: Beschrijving done: Gedaan? due_date: Voor wanneer? @@ -201,7 +213,7 @@ nl: email: E-mail first_name: Voornaam iban: IBAN - last_activity: Laatst actief + last_activity: Laatste activiteit last_login: Laatste aanmelding last_name: Achternaam name: Naam @@ -214,13 +226,13 @@ nl: one: Werkgroep other: Werkgroepen workgroup: - description: Omschrijving + description: Beschrijving name: Naam role_admin: Beheer role_article_meta: Artikelen role_finance: Financiën role_invoices: Facturen - role_orders: Bestellingen + role_orders: Beheer bestellingen role_pickups: Ophaaldagen role_suppliers: Leveranciers user_tokens: Leden @@ -243,6 +255,8 @@ nl: models: article: Artikel article_category: Categorie + bank_account: Bankrekening + bank_gateway: Betalingsdienst bank_transaction: Banktransactie delivery: Levering financial_transaction: Financiële transactie @@ -254,10 +268,11 @@ nl: order_comment: Commentaar ordergroup: one: Huishouden - other: Huishouden + other: Huishoudens stock_article: Voorraadartikel stock_taking: Inventaris supplier: Leverancier + supplier_category: Leverancierscategorie task: Taak user: Gebruiker workgroup: Werkgroep @@ -268,7 +283,7 @@ nl: all_ordergroups: Alle huishoudens all_users: Alle gebruikers all_workgroups: Alle werkgroepen - created_at: gemaakt op + created_at: aangemaakt op first_paragraph: Hier kun je de groepen en gebruikers van Foodsoft beheren. groupname: Groepsnaam members: leden @@ -276,16 +291,24 @@ nl: new_ordergroup: Nieuw huishouden new_user: Nieuwe gebruiker new_workgroup: Nieuwe werkgroep - newest_groups: Nieuwste groepen - newest_users: Nieuwste gebruikers - title: Administratie - type: Type - username: Gebruikersnaam + newest_groups: nieuwste groepen + newest_users: nieuwste gebruikers + title: Beheer + type: type + username: gebruikersnaam + bank_accounts: + form: + title_edit: Bankrekening bewerken + title_new: Nieuwe bankrekening toevoegen + bank_gateways: + form: + title_edit: Betalingsdienst bewerken + title_new: Nieuwe betalingsdienst toevoegen configs: list: key: Sleutel title: Configuratielijst - value: Inhoud + value: Waarde show: submit: Opslaan title: Configuratie @@ -297,7 +320,7 @@ nl: schedule_title: Bestelrooster tab_security: default_roles_title: Toegang tot - default_roles_paragraph: "Ieder lid van de foodcoop heeft standaard toegang tot de volgende onderdelen:" + default_roles_paragraph: 'Ieder lid van de foodcoop heeft standaard toegang tot de volgende onderdelen:' tab_tasks: periodic_title: Periodieke taken tabs: @@ -311,8 +334,11 @@ nl: first_paragraph: Hier kunt u de klassen van financiële transacties en de bijbehorende typen financiële transacties beheren. Elke financiële transactie heeft een type, die je bij elke transactie moet selecteren, als je meer dan één type hebt gemaakt. De klassen financiële transacties kunnen worden gebruikt om de types financiële transacties te groeperen en zullen worden weergegeven als extra kolommen in het rekeningoverzicht, als er meer dan één is gecreëerd. new_bank_account: Nieuwe bankrekening toevoegen new_financial_transaction_class: Nieuwe klasse voor financiële transacties toevoegen + new_bank_gateway: Nieuwe betalingsdienst toevoegen title: Financiën transaction_types: Typen financiële transacties + supplier_categories: Leverancierscategorieën + new_supplier_category: Nieuwe leverancierscategorie transaction_types: name: Naam new_financial_transaction_type: Nieuw type financiële transactie toevoegen @@ -411,6 +437,10 @@ nl: workgroups: members: leden name: naam + supplier_categories: + form: + title_new: Leverancierscategorie toevoegen + title_edit: Leverancierscategorie bewerken application: controller: error_authn: Aanmelden vereist! @@ -569,8 +599,8 @@ nl: street: Adres, meestal is dit het aflever- en ophaaladres. currency_space: Spatie toevoegen na valutasymbool. currency_unit: Valutasymbool voor het tonen van prijzen. - custom_css: De layout van deze site kan gewijzigd worden door hier cascading stylesheets (CSS) in te voeren. Laat het leeg voor de standaardstijl. - email_from: Emails zullen lijken verzonden te zijn vanaf dit email adres. Laat het veld leeg om het contactadres van de foodcoop te gebruiken. + custom_css: Om de lay-out van deze site aan te passen, kunt u stijlwijzigingen invoeren met behulp van cascading stylesheets (CSS). Laat leeg voor de standaardstijl. + email_from: Het zal lijken alsof e-mails verzonden zijn vanaf dit e-mailadres. Laat het veld leeg om het contactadres van de foodcoop te gebruiken. email_replyto: Vul dit in als je antwoord op mails van Foodsoft wilt ontvangen op een ander adres dan het bovenstaande. email_sender: Emails worden verzonden vanaf dit emailadres. Om te voorkomen dat emails als spam worden tegengehouden, is het te adviseren het adres van de webserver op te nemen in het SPF record van het email domein. help_url: Documentatie website. @@ -625,13 +655,13 @@ nl: default_role_orders: Bestellingen default_role_pickups: Ophaaldagen default_role_suppliers: Leveranciers - disable_invite: Uitnodigingen deactiveren + disable_invite: Uitnodigingen uitschakelen disable_members_overview: Ledenlijst deactiveren - email_from: From adres - email_replyto: Reply-to adres - email_sender: Sender adres - help_url: Documentatie URL - homepage: Homepage + email_from: Adres afzender + email_replyto: Antwoord-adres + email_sender: Adres afzender + help_url: URL documentatie + homepage: Hoofdpagina ignore_browser_locale: Browsertaal negeren minimum_balance: Minimum tegoed name: Naam @@ -1310,11 +1340,11 @@ nl: order_result_supplier: subject: Nieuwe bestelling voor %{name} text: | - Beste mijnheer/mevrouw, + Goeiedag, - Foodcoop %{foodcoop} wil graag een bestelling plaatsen. + %{foodcoop} wil graag een bestelling plaatsen. - Een PDF en spreadsheet vind u meegestuurd. + Een PDF en spreadsheet vindt u in bijlage. Met vriendelijke groet, %{user} diff --git a/crowdin.yml b/crowdin.yml index 5e81eec5..f939ea23 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,5 +1,7 @@ project_id: 357447 api_token_env: CROWDIN_API_KEY +preserve_hierarchy: true +base_path: "." files: - source: /config/locales/en.yml translation: /config/locales/%two_letters_code%.yml diff --git a/plugins/current_orders/config/locales/de.yml b/plugins/current_orders/config/locales/de.yml index 4df36568..3e1cfeb8 100644 --- a/plugins/current_orders/config/locales/de.yml +++ b/plugins/current_orders/config/locales/de.yml @@ -10,6 +10,7 @@ de: counts: '%{ordergroups} Bestellgruppen bestellten %{articles} verschiedene Produkte.' no_selection: Wähle einen Artikel, um anzuzeigen wer ihn bestellt hat oder downloade eine Abholliste rechts. article_info: + origin_in: in %{origin} supplied_by: von %{supplier} supplied_and_made_by: produziert von %{manufacturer} supplied_by_made_by: von %{supplier} produziert von %{manufacturer} @@ -24,6 +25,8 @@ de: piece: St. unit: Einheit add_new: Bestellgruppe hinzufügen + show: + title: ! '%{name}' navigation: receive: In Empfang nehmen articles: Verteilen @@ -41,6 +44,7 @@ de: title: Artikel für Bestellgruppe payment_bar: account_balance: Kontostand + new_pin: PIN new_transaction: Neue Transaktion payment: ! 'Zahlung:' show: diff --git a/plugins/current_orders/config/locales/es.yml b/plugins/current_orders/config/locales/es.yml index 04b672b2..3ff1468a 100644 --- a/plugins/current_orders/config/locales/es.yml +++ b/plugins/current_orders/config/locales/es.yml @@ -1,5 +1,7 @@ es: config: + hints: + use_current_orders: Activar el plugin de current_orders. Permite a los miembros con el permiso de la orden cambiar la cantidad de miembros en múltiples pedidos, usando tres nuevas pantallas en el menú de pedidos. Especialmente útil para los días de recogida. keys: use_current_orders: Pantallas extra de distribución current_orders: @@ -23,6 +25,8 @@ es: piece: pieza unit: unidad add_new: Añade un grupo de pedido... + show: + title: ! '%{name}' navigation: receive: Recibe articles: Distribuye @@ -40,6 +44,7 @@ es: title: Artículos por grupo de pedido payment_bar: account_balance: Balance de cuenta + new_pin: PIN new_transaction: Nueva transacción payment: ! 'Pago:' show: diff --git a/plugins/discourse/config/locales/es.yml b/plugins/discourse/config/locales/es.yml index f1e055d2..0da76a94 100644 --- a/plugins/discourse/config/locales/es.yml +++ b/plugins/discourse/config/locales/es.yml @@ -1,5 +1,6 @@ es: discourse: callback: + invalid_nonce: Nonce inválido invalid_signature: Firma inválida logged_in: Estás adentro! diff --git a/plugins/documents/config/locales/de.yml b/plugins/documents/config/locales/de.yml index 39bbcb6f..be56700d 100644 --- a/plugins/documents/config/locales/de.yml +++ b/plugins/documents/config/locales/de.yml @@ -6,6 +6,7 @@ de: created_by: Erstellt von data: Daten mime: MIME-Typ + name: Name config: hints: documents_allowed_extension: Eine Liste an erlaubten Dateiendungen getrennt durch Leerzeichen. diff --git a/plugins/documents/config/locales/es.yml b/plugins/documents/config/locales/es.yml index fe092fdc..02103cc6 100644 --- a/plugins/documents/config/locales/es.yml +++ b/plugins/documents/config/locales/es.yml @@ -19,6 +19,7 @@ es: documents: create: error: 'El documento no puede ser creado: %{error}' + not_allowed_mime: El tipo de archivo "%{mime}" no está permitido. Póngase en contacto con un administrador/a para incluirlo en la lista blanca. notice: Se ha creado el documento destroy: error: 'El documento no puede ser borrado: %{error}' @@ -26,6 +27,14 @@ es: notice: Se ha borrado el documento form: new: Nuevo Documento + new_folder: Carpeta nueva + submit: Crear index: new: Sube nuevo documento + new_folder: Crear una nueva carpeta title: Documentos + move: + root_folder: Iniciar + title: Mover + update: + notice: Documento o carpeta fue movido diff --git a/plugins/messages/config/locales/de.yml b/plugins/messages/config/locales/de.yml index eb8cff21..2f423dc1 100644 --- a/plugins/messages/config/locales/de.yml +++ b/plugins/messages/config/locales/de.yml @@ -20,6 +20,7 @@ de: workgroup_id: Arbeitsgruppe messagegroup: description: Beschreibung + name: Name user_tokens: Mitglieder models: message: Nachricht @@ -84,6 +85,7 @@ de: model: reply_header: ! '%{user} schrieb am %{when}:' reply_indent: ! '> %{line}' + reply_subject: ! 'Re: %{subject}' new: error_private: Nachricht ist privat! hint_private: Nachricht erscheint nicht im Foodsoft Posteingang diff --git a/plugins/messages/config/locales/es.yml b/plugins/messages/config/locales/es.yml index 3b505e79..81a9a206 100644 --- a/plugins/messages/config/locales/es.yml +++ b/plugins/messages/config/locales/es.yml @@ -59,6 +59,7 @@ es: write_message: Escribir un mensaje messagegroups: index: + body: 'Un grupo de mensajes es como una lista de correo: puedes unirte (o salir) a cualquiera de ellos para recibir las actualizaciones enviadas a ese grupo.' title: Grupos de mensaje join: error: 'No pudo unirse al grupo de mensaje: %{error}' @@ -72,27 +73,38 @@ es: messages: actionbar: message_threads: Muestra como hilos + messagegroups: Suscribirse a este grupo messages: Muestra como lista new: Nuevo mensaje + create: + notice: El mensaje ha sido guardado y será enviado. index: title: Mensajes messages: reply: Responde + model: + reply_header: ! '%{user} escribió en %{when}:' + reply_indent: ! '> %{line}' + reply_subject: ! 'Re: %{subject}' new: error_private: Lo siento, este mensaje es privado. + hint_private: El mensaje no se muestra en el buzón de correo Foodsoft list: desc: ! 'Envía mensajes a todos los miembros a través de la lista de correo: %{list}' mail: por ejemplo con un email a %{email}. subscribe: Puedes leer más sobre la lista de correos en %{link}. subscribe_msg: Quizás tengas que suscribirte a la lista primero. + wiki: Wiki (lista de correo de páginas) message: mensaje no_user_found: No se ha encontrado el usuario + order_item: "%{supplier_name} (Recoger: %{pickup})" reply_to: Este mensaje es una respuesta a otro %{link}. search: Busca ... search_user: Busca usuario title: Nuevo mensaje show: all_messages: Todos los mensajes + change_visibility: 'Cambiar' from: ! 'De:' group: 'Grupo:' reply: Responde @@ -101,19 +113,35 @@ es: subject: ! 'Asunto:' title: Muestra mensaje to: 'A:' + visibility: 'Visibilidad:' + visibility_private: 'Privado' + visibility_public: 'Público' thread: all_message_threads: Todos los hilos de mensaje reply: Responde + toggle_private: + not_allowed: No puede cambiar la visibilidad del mensaje. message_threads: groupmessage_threads: show_message_threads: muestra todos index: + general: General title: Hilos de mensaje message_threads: last_reply_at: Última respuesta el - last_reply_by: Última respuesta de + last_reply_by: Última respuesta de started_at: Comenzado el started_by: Comenzado por + show: + general: General + messages_mailer: + foodsoft_message: + footer: | + Respuesta: %{reply_url} + Ver mensaje en línea: %{msg_url} + Opciones de mensaje: %{profile_url} + footer_group: | + Enviado al grupo: %{group} navigation: admin: messagegroups: Grupos de mensaje diff --git a/plugins/wiki/config/locales/de.yml b/plugins/wiki/config/locales/de.yml index cb02e461..dfdf8da4 100644 --- a/plugins/wiki/config/locales/de.yml +++ b/plugins/wiki/config/locales/de.yml @@ -17,6 +17,7 @@ de: wiki: all_pages: Alle Seiten home: Startseite + title: Wiki pages: all: new_page: Neue Seite anlegen @@ -67,6 +68,7 @@ de: section_table: Tabellenformatierung see_tables: siehe %{tables_link} tables_link: Tabellen + text: Text title: Schnelle Formatierungshilfe unordered_list: Listen mit Punkten wiki_link_ex: Foodsoft Wiki Seite @@ -94,9 +96,11 @@ de: description: Variablen geben Informationen wieder, die an anderer Stelle definiert wurden. Wenn Du die Variable benutzt, wird sie mit ihrem Wert ersetzt, wenn sie angezeigt wird. Foodsoft hat eine Reihe von vordefinierten Variablen, wie zum Beispiel Name und Adresse deiner Foodcoop, Softwareversion und die Anzahl der Mitglieder und Lieferanten. In der Tabelle unten sind alle Variablen aufgeführt. Du kannst diese in Wiki-Seiten und der Fusszeile (in den Einstellungen) nutzen. title: Foodsoft-Variablen value: Aktueller Wert + variable: Variable version: author: ! 'Autor: %{user}' date_format: ! '%a, %d.%m.%Y, %H:%M Uhr' revert: Auf diese Version zurücksetzen title: ! '%{title} - Version %{version}' + title_version: Version view_current: Aktuelle Version sehen diff --git a/plugins/wiki/config/locales/es.yml b/plugins/wiki/config/locales/es.yml index 71761e80..a7391e94 100644 --- a/plugins/wiki/config/locales/es.yml +++ b/plugins/wiki/config/locales/es.yml @@ -16,24 +16,36 @@ es: navigation: wiki: all_pages: Todas las páginas + home: Inicio + title: Wiki pages: all: new_page: Crea nueva página recent_changes: Cambios recientes search: action: Busca + placeholder: Título de página .. + site_map: Mapa web + title: Todas las páginas Wiki title_list: Lista de páginas body: title_toc: Contenido + wikicloth_exception: 'Sentimos informar de que ha ocurrido un error al interpretar la página wiki: %{msg}. Por favor, intenta arreglarla y guarda la página de nuevo.' create: notice: La página ha sido creada cshow: error_noexist: La página no existe + redirect_notice: Redirigido desde %{page}... + destroy: + notice: La página '%{page}' y todas las subpáginas se han eliminado correctamente. + diff: + title: "%{title} - cambios de versión %{old} a %{new}" edit: title: Editar página error_stale_object: Cuidado, la página ha sido editada por otra persona. Por favor, intenta de nuevo. form: help: + bold: negrita external_link_ex: Enlace externo external_links: Externo heading: nivel %{level} @@ -41,18 +53,54 @@ es: image_link_title: Título de la imagen image_links: Imágenes italic: itálicas + link_lists: Más en listas + link_table: Formato de tabla + link_templates: Plantillas + link_variables: Variables Foodsoft + list_item_1: Primer elemento de lista + list_item_2: Segundo elemento de lista + noformat: Sin Formato ordered_list: Lista numerada section_block: Estilo del párrafo section_character: Estilo de las letras section_link: Estilo de los enlaces section_more: Más temas + section_table: Formato de tabla + see_tables: ver %{tables_link} + tables_link: Tablas + text: texto + title: Ayuda de formato rápido + unordered_list: Lista de artículos + wiki_link_ex: Página Wiki de Foodsoft + wiki_links: Enlaces Wiki preview: Previsualizar + last_updated: Última actualización new: title: Crear una nueva página en la wiki + page_list_item: + date_format: ! '%a, %d %B %Y %H:%M:%S' show: + date_format: ! '%d-%m-%y %H:%M' delete: Eliminar página + delete_confirm: ! 'Advertencia: todas las subpáginas serán eliminadas también. ¿Estás seguro?' + diff: Comparar versiones + edit: Editar página + last_updated: Última actualización por %{user} el %{when} + subpages: subpáginas + title_versions: Versiones + versions: Versiones (%{count}) title: Título + update: + notice: La página fue actualizada + variables: + description: Las variables devuelven información de otro lugar. Cuando utilice la variable, se reemplazará por su valor cuando se muestre. Foodsoft tiene un número de variables predefinidas, como el nombre y la dirección de tu cooperativa, la versión del software, y el número de miembros y proveedores. Vea la siguiente tabla para todas las variables. Puede usarlas en páginas wiki así como en el pie de página (desde la pantalla de configuración). + title: Variables Foodsoft + value: Valor actual + variable: Variable version: author: ! 'Autor/a: %{user}' + date_format: ! '%a, %d-%m-%Y, %H:%M' + revert: Revertir a esta versión + title: ! '%{title} - versión %{version}' title_version: Versión view_current: Ver versión actual From e1b582483038c9357886a0979c505377b2c27a7c Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Thu, 13 Jul 2023 19:00:30 +0200 Subject: [PATCH 4/7] update changelog v4.8 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f88a1d03..8474e7fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +# Foodsoft 4.8.0 + +* feat: Show total sums for ordergroup finances [#1017](https://github.com/foodcoops/foodsoft/pull/1017) +* feat: Richtext Messages and Attachments with Actiontext [#918](https://github.com/foodcoops/foodsoft/issues/918) +* feat: Make date configurable via locales [#997](https://github.com/foodcoops/foodsoft/pull/997) +* feat: Turkish language support added [#995](https://github.com/foodcoops/foodsoft/pull/995) +* feat: Disable member list via configuration [#990](https://github.com/foodcoops/foodsoft/pull/990) +* feat: Specify an URL to redirect after logout via settings #989 +* feat: introduce importmaps [#983](https://github.com/foodcoops/foodsoft/pull/983) +* feat: ruby 2.7.2 and rails 7 upgrade [#979](https://github.com/foodcoops/foodsoft/pull/979) +* feat: Add home controller test [#972](https://github.com/foodcoops/foodsoft/pull/972) +* feat: Replace apivore with rswag for api tests [#969](https://github.com/foodcoops/foodsoft/pull/969) +* feat: increase test coverage [#966](https://github.com/foodcoops/foodsoft/pull/966) +* feat: Show order note as tooltip [#965](https://github.com/foodcoops/foodsoft/pull/965) +* feat: Add sd_notify [#961](https://github.com/foodcoops/foodsoft/pull/961) +* feat: Show instance name at login screen [#957](https://github.com/foodcoops/foodsoft/pull/957) +* feat: Enabled systemd socket activation [#942](https://github.com/foodcoops/foodsoft/pull/942) +* feat: Add table_print gem for debugging ActiveRecord queries in the console [#935](https://github.com/foodcoops/foodsoft/pull/935) +* feat: Add admin UI for SupplierCategories (supplier_categories) [#930](https://github.com/foodcoops/foodsoft/pull/930) + +* fix: add null checks for articles convert_units [33034e6](https://github.com/foodcoops/foodsoft/commit/33034e66b88968dedc5289425e1eff847ee67e12) +* fix: downgrade haml to make deface work [#1003](https://github.com/foodcoops/foodsoft/pull/1003) +* fix: dutch translation errors [#954](https://github.com/foodcoops/foodsoft/pull/954) +* fix: Fixe filtering of active ordergroups [#934](https://github.com/foodcoops/foodsoft/pull/934) +* fix: Change password validation to allow longer passwords [#923](https://github.com/foodcoops/foodsoft/pull/923) +* fix: Invoice: change label "delivery" to "stock delivery" [#922](https://github.com/foodcoops/foodsoft/pull/922) +* fix: Allow decimal numbers in transaction collections [#921](https://github.com/foodcoops/foodsoft/pull/921) +* fix: Add validation of more article fields [#917](https://github.com/foodcoops/foodsoft/pull/917/files) +* fix: Add default time_zone [#912](https://github.com/foodcoops/foodsoft/pull/912) +* fix: Rename Piwik to Matomo [#911](https://github.com/foodcoops/foodsoft/pull/911/files) +* fix: Change instructions to rbenv [#910](https://github.com/foodcoops/foodsoft/pull/910/files) + + # Foodsoft 4.7.1 (31 December 2020) From e194c683978dd783972ef75b45319a6bbf7ad62b Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Sat, 9 Sep 2023 10:49:15 +0200 Subject: [PATCH 5/7] chore: bump version to 4.8.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3d512a04..88f18119 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.7.99 +4.8.0 From 55234b4e271814827c8084a84814ffec0f0f4a43 Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Sat, 9 Sep 2023 17:01:48 +0200 Subject: [PATCH 6/7] continue development after release --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 88f18119..5003783a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.8.0 +4.8.99 From 2d0c163f13b1042d1f8fb45afe6814e16cdc28d0 Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Mon, 24 Jul 2023 10:50:35 +0200 Subject: [PATCH 7/7] merge automatic group order invoice generation see https://github.com/foodcoops/foodsoft/pull/907 for reference and original work by viehlieb Co-authored-by: viehlieb fix PDF Pdf make explicit deposit in invoices work add ordergroupname to invoice file name mark bold sum for vat exempt foodcoops download multiple group order invoice as zip --- .rubocop_todo.yml | 3 + .../bootstrap_and_overrides.css.less | 9 +- .../concerns/send_group_order_invoice_pdf.rb | 17 ++ .../finance/balancing_controller.rb | 23 +- .../group_order_invoices_controller.rb | 87 ++++++ app/documents/group_order_invoice_pdf.rb | 264 ++++++++++++++++++ app/jobs/notify_group_order_invoice_job.rb | 10 + app/lib/render_pdf.rb | 21 +- app/mailers/mailer.rb | 17 ++ app/models/concerns/price_calculation.rb | 16 ++ app/models/group_order.rb | 1 + app/models/group_order_article.rb | 12 + app/models/group_order_invoice.rb | 58 ++++ app/models/order.rb | 14 +- .../admin/configs/_tab_foodcoop.html.haml | 1 + .../admin/configs/_tab_payment.html.haml | 6 + app/views/admin/ordergroups/_form.html.haml | 1 + app/views/finance/balancing/_orders.html.haml | 10 + app/views/finance/balancing/_summary.haml | 10 + app/views/finance/balancing/index.html.haml | 1 - .../group_order_invoices/_links.html.haml | 29 ++ app/views/group_order_invoices/create.js.erb | 1 + .../create_multiple.js.erb | 1 + app/views/group_order_invoices/destroy.js.erb | 1 + app/views/group_orders/_form.html.haml | 4 +- .../mailer/group_order_invoice.text.haml | 1 + app/views/ordergroups/edit.html.haml | 4 + app/views/shared/_group.html.haml | 2 + config/locales/de.yml | 82 ++++++ config/locales/en.yml | 79 ++++++ config/routes.rb | 7 + ...11208142719_create_group_order_invoices.rb | 13 + ...0822120005_add_customer_number_to_group.rb | 5 + db/schema.rb | 109 ++++---- spec/factories/group_order_invoice.rb | 7 + spec/integration/group_order_invoices_spec.rb | 72 +++++ spec/models/group_order_invoice_spec.rb | 59 ++++ 37 files changed, 988 insertions(+), 69 deletions(-) create mode 100644 app/controllers/concerns/send_group_order_invoice_pdf.rb create mode 100644 app/controllers/group_order_invoices_controller.rb create mode 100644 app/documents/group_order_invoice_pdf.rb create mode 100644 app/jobs/notify_group_order_invoice_job.rb create mode 100644 app/models/group_order_invoice.rb create mode 100644 app/views/group_order_invoices/_links.html.haml create mode 100644 app/views/group_order_invoices/create.js.erb create mode 100644 app/views/group_order_invoices/create_multiple.js.erb create mode 100644 app/views/group_order_invoices/destroy.js.erb create mode 100644 app/views/mailer/group_order_invoice.text.haml create mode 100644 db/migrate/20211208142719_create_group_order_invoices.rb create mode 100644 db/migrate/20230822120005_add_customer_number_to_group.rb create mode 100644 spec/factories/group_order_invoice.rb create mode 100644 spec/integration/group_order_invoices_spec.rb create mode 100644 spec/models/group_order_invoice_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index cbbec263..5276a743 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -274,6 +274,8 @@ Lint/Void: # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 143 + Exclude: + - 'app/documents/group_order_invoice_pdf.rb' # Offense count: 13 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. @@ -407,6 +409,7 @@ RSpec/Capybara/FeatureMethods: - 'spec/integration/receive_spec.rb' - 'spec/integration/session_spec.rb' - 'spec/integration/supplier_spec.rb' + - 'spec/integration/group_order_invoices_spec.rb' # Offense count: 4 RSpec/Capybara/SpecificMatcher: diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 971308c9..ebd30b20 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -241,6 +241,9 @@ table { tr.order-article:hover .article-info { display: none; } + tr.order-article:focus .article-info { + display: none; + } } #order-footer { @@ -275,11 +278,13 @@ tr.order-article .article-info { display: none; } -tr.order-article:hover .article-info { +tr.order-article:focus{ + background-color: #E4EED6; +} +tr.order-article:focus .article-info { display: block; } - // ********* Articles tr.just-updated { diff --git a/app/controllers/concerns/send_group_order_invoice_pdf.rb b/app/controllers/concerns/send_group_order_invoice_pdf.rb new file mode 100644 index 00000000..76e71c99 --- /dev/null +++ b/app/controllers/concerns/send_group_order_invoice_pdf.rb @@ -0,0 +1,17 @@ +module Concerns::SendGroupOrderInvoicePdf + extend ActiveSupport::Concern + + protected + + def create_invoice_pdf(group_order_invoice) + invoice_data = group_order_invoice.load_data_for_invoice + invoice_data[:title] = t('documents.group_order_invoice_pdf.title', supplier: invoice_data[:supplier]) + invoice_data[:no_footer] = true + GroupOrderInvoicePdf.new invoice_data + end + + def send_group_order_invoice_pdf(group_order_invoice) + pdf = create_invoice_pdf(group_order_invoice) + send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf' + end +end diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb index e1a2dafb..29320c64 100644 --- a/app/controllers/finance/balancing_controller.rb +++ b/app/controllers/finance/balancing_controller.rb @@ -5,7 +5,7 @@ class Finance::BalancingController < Finance::BaseController def new @order = Order.find(params[:order_id]) - flash.now.alert = t('.alert') if @order.closed? + flash.now.alert = t('finance.balancing.new.alert') if @order.closed? && flash[:alert].blank? @comments = @order.comments @articles = @order.order_articles.ordered_or_member.includes(:article, :article_price, @@ -81,9 +81,24 @@ class Finance::BalancingController < Finance::BaseController @order = Order.find(params[:id]) @type = FinancialTransactionType.find_by_id(params.permit(:type)[:type]) @order.close!(@current_user, @type) - redirect_to finance_order_index_url, notice: t('.notice') - rescue StandardError => e - redirect_to new_finance_order_url(order_id: @order.id), alert: t('.alert', message: e.message) + note = t('finance.balancing.close.notice') + if @order.closed? + alert = t('finance.balancing.close.alert') + if FoodsoftConfig[:group_order_invoices]&.[](:use_automatic_invoices) + @order.group_orders.each do |go| + alert = t('finance.balancing.close.settings_not_set') + goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) + if goi.save! + NotifyGroupOrderInvoiceJob.perform_later(goi) + note = t('finance.balancing.close.notice_mail') + end + end + end + end + alert ||= t('finance.balancing.close.alert') + redirect_to finance_order_index_url, notice: note + rescue => error + redirect_to new_finance_order_url(order_id: @order.id), notice: note, alert: alert, msg: error.message end # Close the order directly, without automaticly updating ordergroups account balances diff --git a/app/controllers/group_order_invoices_controller.rb b/app/controllers/group_order_invoices_controller.rb new file mode 100644 index 00000000..2e5a8408 --- /dev/null +++ b/app/controllers/group_order_invoices_controller.rb @@ -0,0 +1,87 @@ +class GroupOrderInvoicesController < ApplicationController + include Concerns::SendGroupOrderInvoicePdf + before_action :authenticate_finance + + def show + begin + @group_order_invoice = GroupOrderInvoice.find(params[:id]) + if FoodsoftConfig[:contact][:tax_number] + respond_to do |format| + format.pdf do + send_group_order_invoice_pdf @group_order_invoice if FoodsoftConfig[:contact][:tax_number] + end + end + else + raise RecordInvalid + end + rescue ActiveRecord::RecordInvalid => error + redirect_back fallback_location: root_path, notice: 'Something went wrong', alert: I18n.t('errors.general_msg', msg: "#{error} " + I18n.t('errors.check_tax_number')) + end + end + + def destroy + goi = GroupOrderInvoice.find(params[:id]) + @order = goi.group_order.order + goi.destroy + respond_to do |format| + format.js + format.json { head :no_content } + end + end + + def create_multiple + invoice_date = params[:group_order_invoice][:invoice_date] + order_id = params[:group_order_invoice][:order_id] + @order = Order.find(order_id) + gos = GroupOrder.where("order_id = ?", order_id) + gos.each do |go| + goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) + goi.invoice_date = invoice_date + goi.invoice_number = goi.generate_invoice_number(1) + goi.save! + end + respond_to do |format| + format.js + end + end + + def create + go = GroupOrder.find(params[:group_order]) + @order = go.order + GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) + respond_to do |format| + format.js + end + redirect_back fallback_location: root_path + rescue => error + redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => error) + end + + def download_all + order = Order.find(params[:order_id]) + + invoices = order.group_orders.map(&:group_order_invoice) + pdf = {} + + temp_file = Tempfile.new("all_invoices_for_order_#{order.id}.zip") + + Zip::File.open(temp_file.path, Zip::File::CREATE) do |zipfile| + invoices.each do |invoice| + pdf = create_invoice_pdf(invoice) + invoice_file = Tempfile.new("#{pdf.filename}") + File.open(invoice_file.path, 'w:ASCII-8BIT') do |file| + file.write(pdf.to_pdf) + end + zipfile.add("#{pdf.filename}", invoice_file.path) unless zipfile.find_entry("#{pdf.filename}") + end + end + + zip_data = File.read(temp_file.path) + + respond_to do |format| + format.html { + send_data(zip_data, type: 'application/zip', filename: "#{l order.ends, format: :file}-#{order.supplier.name}-#{order.id}.zip", disposition: 'attachment') + } + end + end +end diff --git a/app/documents/group_order_invoice_pdf.rb b/app/documents/group_order_invoice_pdf.rb new file mode 100644 index 00000000..899d6cf8 --- /dev/null +++ b/app/documents/group_order_invoice_pdf.rb @@ -0,0 +1,264 @@ +class GroupOrderInvoicePdf < RenderPdf + def filename + ordergroup_name = @options[:ordergroup].name || "OrderGroup" + "#{ordergroup_name}_" + I18n.t('documents.group_order_invoice_pdf.filename', :number => @options[:invoice_number]) + '.pdf' + end + + def title + I18n.t('documents.group_order_invoice_pdf.title', :supplier => @options[:supplier]) + end + + def body + contact = FoodsoftConfig[:contact].symbolize_keys + ordergroup = @options[:ordergroup] + + # From paragraph + bounding_box [margin_box.right - 200, margin_box.top - 20], width: 200 do + text I18n.t('documents.group_order_invoice_pdf.invoicer') + move_down 7 + text FoodsoftConfig[:name], size: fontsize(9), align: :left + move_down 5 + text contact[:street], size: fontsize(9), align: :left + move_down 5 + text "#{contact[:zip_code]} #{contact[:city]}", size: fontsize(9), align: :left + move_down 5 + if contact[:phone].present? + text "#{Supplier.human_attribute_name :phone}: #{contact[:phone]}", size: fontsize(9), align: :left + move_down 5 + end + text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), align: :left if contact[:email].present? + move_down 5 + text I18n.t('documents.group_order_invoice_pdf.tax_number', :number => @options[:tax_number]), size: fontsize(9), align: :left + end + + # Receiving Ordergroup + bounding_box [margin_box.left, margin_box.top - 20], width: 200 do + text I18n.t('documents.group_order_invoice_pdf.invoicee') + move_down 7 + text I18n.t('documents.group_order_invoice_pdf.ordergroup.name', ordergroup: ordergroup.name.to_s), size: fontsize(9) + move_down 5 + if ordergroup.contact_address.present? + text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_address', contact_address: ordergroup.contact_address.to_s), size: fontsize(9) + move_down 5 + end + if ordergroup.contact_phone.present? + text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_phone', contact_phone: ordergroup.contact_phone.to_s), size: fontsize(9) + move_down 5 + end + if ordergroup.customer_number.present? + text I18n.t('documents.group_order_invoice_pdf.ordergroup.customer_number', customer_number: ordergroup.customer_number.to_s), size: fontsize(9) + move_down 5 + end + end + + # invoice Date and nnvoice number + bounding_box [margin_box.right - 200, margin_box.top - 150], width: 200 do + text I18n.t('documents.group_order_invoice_pdf.invoice_date', invoice_date: @options[:invoice_date].strftime(I18n.t('date.formats.default'))), align: :left + move_down 5 + text I18n.t('documents.group_order_invoice_pdf.invoice_number', invoice_number: @options[:invoice_number]), align: :left + end + + move_down 15 + + # kind of the "body" of the invoice + text I18n.t('documents.group_order_invoice_pdf.payment_method', payment_method: @options[:payment_method]) + move_down 15 + text I18n.t('documents.group_order_invoice_pdf.table_headline') + move_down 5 + + #------------- Table Data ----------------------- + + @group_order = GroupOrder.find(@options[:group_order].id) + + if FoodsoftConfig[:group_order_invoices][:vat_exempt] + body_for_vat_exempt + else + body_with_vat + end + end + + def body_for_vat_exempt + total_gross = 0 + data = [I18n.t('documents.group_order_invoice_pdf.vat_exempt_rows')] + move_down 10 + group_order_articles = GroupOrderArticle.where(group_order_id: @group_order.id) + separate_deposits = FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits) + group_order_articles.each do |goa| + # if no unit is received, nothing is to be charged + next if goa.result.to_i == 0 + + goa_total_price = separate_deposits ? goa.total_price_without_deposit : goa.total_price + data << [goa.order_article.article.name, + goa.result.to_i, + number_to_currency(goa.order_article.price.fc_price_without_deposit), + number_to_currency(goa_total_price)] + total_gross += goa_total_price + next unless separate_deposits && goa.order_article.price.deposit > 0.0 + + goa_total_deposit = goa.result * goa.order_article.price.fc_deposit_price + data << ["zzgl. Pfand", + goa.result.to_i, + number_to_currency(goa.order_article.article.fc_deposit_price), + number_to_currency(goa_total_deposit)] + total_gross += goa_total_deposit + end + + table data, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| + table.header = true + table.position = :center + table.cells.border_width = 1 + table.cells.border_color = '666666' + + table.row(0).column(0..4).width = 80 + table.row(0).border_bottom_width = 2 + table.columns(1).align = :right + table.columns(1..6).align = :right + end + + move_down 5 + sum = [] + sum << [nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(total_gross)] + # table for sum + table sum, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| + table.header = true + table.position = :center + table.cells.border_width = 1 + table.cells.border_color = '666666' + table.row(0).columns(2..4).style(align: :bottom) + table.row(0).border_bottom_width = 2 + table.row(0..-1).columns(0..1).border_width = 0 + + table.rows(0..-1).columns(0..4).width = 80 + table.row(0).column(-1).style(font_style: :bold) + table.row(0).column(-2).style(font_style: :bold) + table.row(0).column(-1).size = fontsize(10) + table.row(0).column(-2).size = fontsize(10) + + table.columns(1).align = :right + table.columns(1..6).align = :right + end + + move_down 25 + text I18n.t('documents.group_order_invoice_pdf.small_business_regulation') + move_down 10 + end + + def body_with_vat + separate_deposits = FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits) + total_gross = 0 + total_net = 0 + # Articles + + tax_hash_net = Hash.new(0) # for summing up article net prices grouped into vat percentage + tax_hash_gross = Hash.new(0) # same here with gross prices + + if separate_deposits + total_deposit = 0 + total_deposit_gross = 0 + + tax_hash_deposit_gross = Hash.new(0) # for summing up deposit gross prices grouped into vat percentage + tax_hash_deposit_net = Hash.new(0) # same here with gross prices + end + + marge = FoodsoftConfig[:price_markup] + + # data table looks different when price_markup > 0 + data = if marge == 0 + [I18n.t('documents.group_order_invoice_pdf.no_price_markup_rows')] + else + [I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)] + end + goa_tax_hash = GroupOrderArticle.where(group_order_id: @group_order.id).find_each.group_by { |oat| oat.order_article.price.tax } + goa_tax_hash.each do |tax, group_order_articles| + group_order_articles.each do |goa| + # if no unit is received, nothing is to be charged + next if goa.result.to_i == 0 + + order_article = goa.order_article + goa_total_net = goa.result * order_article.price.price + + goa_total_gross = separate_deposits ? goa.total_price_without_deposit : goa.total_price + + data << [order_article.article.name, + goa.result.to_i, + number_to_currency(order_article.price.price), + number_to_currency(goa_total_net), + tax.to_s + '%', + number_to_currency(goa_total_gross)] + + if separate_deposits && order_article.price.deposit > 0.0 + goa_deposit = goa.result * order_article.price.deposit + goa_total_deposit = goa.result * order_article.price.fc_deposit_price + + data << ["zzgl. Pfand", + goa.result.to_i, + number_to_currency(order_article.price.deposit), + number_to_currency(goa_deposit), + tax.to_s + '%', + number_to_currency(goa_total_deposit)] + + total_deposit += goa_deposit + total_deposit_gross += goa_total_deposit + + tax_hash_deposit_net[tax.to_i] += goa_deposit + tax_hash_deposit_gross[tax.to_i] += goa_total_deposit + end + + tax_hash_net[tax.to_i] += goa_total_net + tax_hash_gross[tax.to_i] += goa_total_gross + + total_net += goa_total_net + total_gross += goa_total_gross + end + end + + # Two separate tables for sum and individual data + # article information + data + table data, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| + table.header = true + table.position = :center + table.cells.border_width = 1 + table.cells.border_color = '666666' + table.row(0).columns(0..6).style(background_color: 'cccccc', font_style: :bold) + table.rows(0..-1).columns(0..6).width = 80 + table.row(0).border_bottom_width = 2 + table.columns(1).align = :right + table.columns(1..6).align = :right + end + + sum = [[nil, nil, nil, "Netto", "MwSt", "Brutto"]] + [7, 19].each do |key| + sum << [nil, nil, "Produkte mit #{key}%", number_to_currency(tax_hash_net[key]), number_to_currency(tax_hash_gross[key] - tax_hash_net[key]), number_to_currency(tax_hash_gross[key])] + sum << [nil, nil, "Pfand mit #{key}%", number_to_currency(tax_hash_deposit_net[key]), number_to_currency(tax_hash_deposit_gross[key] - tax_hash_deposit_net[key]), number_to_currency(tax_hash_deposit_gross[key])] if separate_deposits + end + + total_deposit_gross ||= 0 + sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(total_gross + total_deposit_gross)] + + move_down 10 + table sum, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| + table.header = true + table.position = :center + table.cells.border_width = 1 + table.cells.border_color = '666666' + table.row(0).columns(2..6).style(align: :bottom) + table.row(0).border_bottom_width = 2 + table.row(0..-1).columns(0..1).border_width = 0 + + table.rows(0..-1).columns(0..6).width = 80 + table.row(-1).column(-1).style(font_style: :bold) + table.row(-1).column(-2).style(font_style: :bold) + table.row(-1).column(-1).size = fontsize(10) + table.row(-1).column(-2).size = fontsize(10) + + table.columns(1).align = :right + table.columns(1..6).align = :right + end + + if FoodsoftConfig[:group_order_invoices][:vat_exempt] + move_down 15 + text I18n.t('documents.group_order_invoice_pdf.small_business_regulation') + end + move_down 10 + end +end diff --git a/app/jobs/notify_group_order_invoice_job.rb b/app/jobs/notify_group_order_invoice_job.rb new file mode 100644 index 00000000..1a17fe9a --- /dev/null +++ b/app/jobs/notify_group_order_invoice_job.rb @@ -0,0 +1,10 @@ +class NotifyGroupOrderInvoiceJob < ApplicationJob + def perform(group_order_invoice) + ordergroup = group_order_invoice.group_order.ordergroup + ordergroup.users.each do |user| + Mailer.deliver_now_with_user_locale user do + Mailer.group_order_invoice(group_order_invoice, user) + end + end + end +end diff --git a/app/lib/render_pdf.rb b/app/lib/render_pdf.rb index 2311e646..a4974fdf 100644 --- a/app/lib/render_pdf.rb +++ b/app/lib/render_pdf.rb @@ -70,7 +70,7 @@ class RenderPdf < Prawn::Document options[:skip_page_creation] = true @options = options @first_page = true - + no_footer = @options&.[](:no_footer) ? true : false super(options) # Use ttf for better utf-8 compability @@ -84,11 +84,11 @@ class RenderPdf < Prawn::Document ) header = options[:title] || title - footer = I18n.l(Time.now, format: :long) + footer = I18n.l(Time.now, format: :long) unless no_footer header_size = 0 header_size = height_of(header, size: HEADER_FONT_SIZE, font: DEFAULT_FONT) + HEADER_SPACE if header - footer_size = height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE + footer_size = no_footer ? 0 : height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE start_new_page(top_margin: TOP_MARGIN + header_size, bottom_margin: BOTTOM_MARGIN + footer_size) @@ -98,12 +98,15 @@ class RenderPdf < Prawn::Document bounding_box [bounds.left, bounds.top + header_size], width: bounds.width, height: header_size do text header, size: HEADER_FONT_SIZE, align: :center, overflow: :shrink_to_fit if header end - font_size FOOTER_FONT_SIZE do - bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do - text footer, align: :left, valign: :bottom - end - bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do - text I18n.t('lib.render_pdf.page', number: page_number, count: page_count), align: :right, valign: :bottom + + unless no_footer + font_size FOOTER_FONT_SIZE do + bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do + text footer, align: :left, valign: :bottom + end + bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do + text I18n.t('lib.render_pdf.page', number: page_number, count: page_count), align: :right, valign: :bottom + end end end end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index 90c8a062..2b06ce8e 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -51,6 +51,18 @@ class Mailer < ActionMailer::Base subject: I18n.t('mailer.welcome.subject') end + # Sends automatically generated invoicesfor group orders to ordergroup members + def group_order_invoice(group_order_invoice, user) + @user = user + @group_order_invoice = group_order_invoice + @group_order = group_order_invoice.group_order + @supplier = @group_order.order.supplier.name + @group = @group_order.ordergroup + add_group_order_invoice_attachments(group_order_invoice) + mail to: user, + subject: I18n.t('mailer.group_order_invoice.subject', group: @group.name, supplier: @supplier) + end + # Sends order result for specific Ordergroup def order_result(user, group_order) @order = group_order.order @@ -169,6 +181,11 @@ class Mailer < ActionMailer::Base attachments['order.csv'] = OrderCsv.new(order, options).to_csv end + def add_group_order_invoice_attachments(group_order_invoice) + attachment_name = group_order_invoice.name + '.pdf' + attachments[attachment_name] = GroupOrderInvoicePdf.new(group_order_invoice.load_data_for_invoice).to_pdf + end + # separate method to allow plugins to mess with the text def additonal_welcome_text(user); end diff --git a/app/models/concerns/price_calculation.rb b/app/models/concerns/price_calculation.rb index 8d56d671..47c28bc6 100644 --- a/app/models/concerns/price_calculation.rb +++ b/app/models/concerns/price_calculation.rb @@ -7,11 +7,27 @@ module PriceCalculation add_percent(price + deposit, tax) end + def gross_price_without_deposit + add_percent(price, tax) + end + + def gross_deposit_price + add_percent(deposit, tax) + end + # @return [Number] Price for the foodcoop-member. def fc_price add_percent(gross_price, FoodsoftConfig[:price_markup].to_i) end + def fc_price_without_deposit + add_percent(gross_price_without_deposit, FoodsoftConfig[:price_markup].to_i) + end + + def fc_deposit_price + add_percent(gross_deposit_price, FoodsoftConfig[:price_markup].to_i) + end + private def add_percent(value, percent) diff --git a/app/models/group_order.rb b/app/models/group_order.rb index 183b663a..4768a0bd 100644 --- a/app/models/group_order.rb +++ b/app/models/group_order.rb @@ -9,6 +9,7 @@ class GroupOrder < ApplicationRecord has_many :group_order_articles, dependent: :destroy has_many :order_articles, through: :group_order_articles has_one :financial_transaction + has_one :group_order_invoice belongs_to :updated_by, optional: true, class_name: 'User', foreign_key: 'updated_by_user_id' validates :order_id, presence: true diff --git a/app/models/group_order_article.rb b/app/models/group_order_article.rb index 7b95d462..f5f42789 100644 --- a/app/models/group_order_article.rb +++ b/app/models/group_order_article.rb @@ -208,6 +208,18 @@ class GroupOrderArticle < ApplicationRecord end end + def total_price_without_deposit(order_article = self.order_article) + if order_article.order.open? + if FoodsoftConfig[:tolerance_is_costly] + order_article.price.fc_price_without_deposit * (quantity + tolerance) + else + order_article.price.fc_price_without_deposit * quantity + end + else + order_article.price.fc_price_without_deposit * result + end + end + # Check if the result deviates from the result_computed def result_manually_changed? result != result_computed unless result.nil? diff --git a/app/models/group_order_invoice.rb b/app/models/group_order_invoice.rb new file mode 100644 index 00000000..21557161 --- /dev/null +++ b/app/models/group_order_invoice.rb @@ -0,0 +1,58 @@ +class GroupOrderInvoice < ApplicationRecord + belongs_to :group_order + validates_presence_of :group_order + validates_uniqueness_of :invoice_number + validate :tax_number_set + after_initialize :init, unless: :persisted? + + def generate_invoice_number(count) + trailing_number = count.to_s.rjust(4, '0') + if GroupOrderInvoice.find_by(invoice_number: self.invoice_date.strftime("%Y%m%d") + trailing_number) + generate_invoice_number(count.to_i + 1) + else + self.invoice_date.strftime("%Y%m%d") + trailing_number + end + end + + def tax_number_set + if FoodsoftConfig[:contact][:tax_number].blank? + errors.add(:group_order_invoice, "Keine Steuernummer in FoodsoftConfig :contact gesetzt") + end + end + + def init + self.invoice_date = Time.now unless invoice_date + self.invoice_number = generate_invoice_number(1) unless self.invoice_number + self.payment_method = FoodsoftConfig[:group_order_invoices]&.[](:payment_method) || I18n.t('activerecord.attributes.group_order_invoice.payment_method') unless self.payment_method + end + + def name + I18n.t('activerecord.attributes.group_order_invoice.name') + "_#{invoice_number}" + end + + def load_data_for_invoice + invoice_data = {} + order = group_order.order + invoice_data[:supplier] = order.supplier.name + invoice_data[:ordergroup] = group_order.ordergroup + invoice_data[:group_order] = group_order + invoice_data[:invoice_number] = invoice_number + invoice_data[:invoice_date] = invoice_date + invoice_data[:tax_number] = FoodsoftConfig[:contact][:tax_number] + invoice_data[:payment_method] = payment_method + invoice_data[:order_articles] = {} + group_order.order_articles.each do |order_article| + # Get the result of last time ordering, if possible + goa = group_order.group_order_articles.detect { |tmp_goa| tmp_goa.order_article_id == order_article.id } + + # Build hash with relevant data + invoice_data[:order_articles][order_article.id] = { + :price => order_article.article.fc_price, + :quantity => (goa ? goa.quantity : 0), + :total_price => (goa ? goa.total_price : 0), + :tax => order_article.article.tax + } + end + invoice_data + end +end diff --git a/app/models/order.rb b/app/models/order.rb index ada62e59..e275519b 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -207,7 +207,7 @@ class Order < ApplicationRecord # :fc, guess what... def sum(type = :gross) total = 0 - if %i[net gross fc].include?(type) + if %i[net gross gross_deposit fc_deposit deposit fc].include?(type) for oa in order_articles.ordered.includes(:article, :article_price) quantity = oa.units * oa.price.unit_quantity case type @@ -217,6 +217,12 @@ class Order < ApplicationRecord total += quantity * oa.price.gross_price when :fc total += quantity * oa.price.fc_price + when :gross_deposit + total += quantity * oa.price.gross_deposit_price + when :fc_deposit + total += quantity * oa.price.fc_deposit_price + when :deposit + total += quantity * oa.price.deposit end end elsif %i[groups groups_without_markup].include?(type) @@ -224,7 +230,11 @@ class Order < ApplicationRecord for goa in go.group_order_articles case type when :groups - total += goa.result * goa.order_article.price.fc_price + total += if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits) + goa.result * (goa.order_article.price.fc_price + goa.order_article.price.fc_deposit_price) + else + goa.result * goa.order_article.price.fc_price + end when :groups_without_markup total += goa.result * goa.order_article.price.gross_price end diff --git a/app/views/admin/configs/_tab_foodcoop.html.haml b/app/views/admin/configs/_tab_foodcoop.html.haml index 5aea80c2..efea4d81 100644 --- a/app/views/admin/configs/_tab_foodcoop.html.haml +++ b/app/views/admin/configs/_tab_foodcoop.html.haml @@ -7,4 +7,5 @@ = config_input c, :country, as: :string, input_html: {class: 'input-xlarge'} = config_input c, :email, required: true, input_html: {class: 'input-xlarge'} = config_input c, :phone, input_html: {class: 'input-medium'} + = config_input c, :tax_number, input_html: {class: 'input-medium'} = config_input form, :homepage, required: true, as: :url, input_html: {class: 'input-xlarge'} diff --git a/app/views/admin/configs/_tab_payment.html.haml b/app/views/admin/configs/_tab_payment.html.haml index 3fd7ca0a..70291110 100644 --- a/app/views/admin/configs/_tab_payment.html.haml +++ b/app/views/admin/configs/_tab_payment.html.haml @@ -13,6 +13,12 @@ = config_input form, :charge_members_manually, as: :boolean = config_input form, :use_iban, as: :boolean = config_input form, :use_self_service, as: :boolean +%h4= t '.group_order_invoices' += form.fields_for :group_order_invoices do |field| + = config_input field, :use_automatic_invoices, as: :boolean + = config_input field, :separate_deposits, as: :boolean + = config_input field, :vat_exempt, as: :boolean + = config_input field, :payment_method, as: :string, input_html: {class: 'input-medium'} %h4= t '.schedule_title' = form.simple_fields_for :order_schedule do |fields| diff --git a/app/views/admin/ordergroups/_form.html.haml b/app/views/admin/ordergroups/_form.html.haml index 3eb3a9f5..128338bb 100644 --- a/app/views/admin/ordergroups/_form.html.haml +++ b/app/views/admin/ordergroups/_form.html.haml @@ -2,6 +2,7 @@ %p= t('.first_paragraph', url: link_to(t('.here'), new_invite_path(id: @ordergroup.id), remote: true)).html_safe = simple_form_for [:admin, @ordergroup] do |f| - captured = capture do + = f.input :customer_number = f.input :contact_person = f.input :contact_phone = f.input :contact_address diff --git a/app/views/finance/balancing/_orders.html.haml b/app/views/finance/balancing/_orders.html.haml index 3f20d850..dddb00c2 100644 --- a/app/views/finance/balancing/_orders.html.haml +++ b/app/views/finance/balancing/_orders.html.haml @@ -9,6 +9,8 @@ %th= t('.end') %th= t('.state') %th= heading_helper Order, :updated_by + %th= heading_helper GroupOrderInvoice, :name + %th %th %tbody - @orders.each do |order| @@ -17,6 +19,14 @@ %td=h format_time(order.ends) unless order.ends.nil? %td= order.closed? ? t('.cleared', amount: number_to_currency(order.foodcoop_result)) : t('.ended') %td= show_user(order.updated_by) + %td{id: "generate-invoice#{order.id}"} + - if order.closed? + -if FoodsoftConfig[:contact][:tax_number] && order.ordergroups.present? + = render :partial => 'group_order_invoices/links', locals:{order: order} + -else + = I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set') + - else + = t('orders.index.not_closed') %td - unless order.closed? - if current_user.role_orders? diff --git a/app/views/finance/balancing/_summary.haml b/app/views/finance/balancing/_summary.haml index e466727f..6516aa69 100644 --- a/app/views/finance/balancing/_summary.haml +++ b/app/views/finance/balancing/_summary.haml @@ -12,6 +12,16 @@ %tr %td= t('.fc_amount') %td.numeric= number_to_currency(order.sum(:fc)) + - if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits) + %tr + %td= t('.deposit') + %td.numeric= number_to_currency(order.sum(:deposit)) + %tr + %td= t('.gross_deposit') + %td.numeric= number_to_currency(order.sum(:gross_deposit)) + %tr + %td= t('.fc_deposit') + %td.numeric= number_to_currency(order.sum(:fc_deposit)) %tr %td= t('.groups_amount') %td.numeric= number_to_currency(order.sum(:groups)) diff --git a/app/views/finance/balancing/index.html.haml b/app/views/finance/balancing/index.html.haml index 1d1fd8b5..4a7fd119 100644 --- a/app/views/finance/balancing/index.html.haml +++ b/app/views/finance/balancing/index.html.haml @@ -1,5 +1,4 @@ - title t('.title') - - content_for :actionbar do - if FoodsoftConfig[:charge_members_manually] = link_to t('.close_all_direct_with_invoice'), close_all_direct_with_invoice_finance_order_index_path, method: :post, class: 'btn' diff --git a/app/views/group_order_invoices/_links.html.haml b/app/views/group_order_invoices/_links.html.haml new file mode 100644 index 00000000..9e55cedf --- /dev/null +++ b/app/views/group_order_invoices/_links.html.haml @@ -0,0 +1,29 @@ +.row + .column.small-12 + - show_generate_with_date = true + - order.group_orders.each do |go| + - if go.group_order_invoice.present? + - show_generate_with_date = false + - if show_generate_with_date + = form_for :group_order_invoice, url: url_for('group_order_invoice#create_multiple'), remote: true do |f| + = f.label :invoice_date, I18n.t('activerecord.attributes.group_order_invoice.links.invoice_date') + = f.date_field :invoice_date, {value: Date.today, max: Date.today, required: true} + = f.hidden_field :order_id, value: order.id + = f.submit I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date'), class: 'btn btn small' + +- order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go| + .row + .column.small-3 + = label_tag go.ordergroup.name + - if go.group_order_invoice + .column.small-3 + = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download'), group_order_invoice_path(go.group_order_invoice, :format => 'pdf'), class: 'btn btn-small' + .column.small-3 + = link_to I18n.t('activerecord.attributes.group_order_invoice.links.delete'), go.group_order_invoice, method: :delete, class: 'btn btn-danger btn-small', remote: true + - else + = button_to I18n.t('activerecord.attributes.group_order_invoice.links.generate'), group_order_invoices_path(:method => :post, group_order: go) ,class: 'btn btn-small', params: {id: order.id}, remote: true +- if order.group_orders.map(&:group_order_invoice).compact.present? + %br/ + .row + .column.small-3 + = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download_all_zip'), download_all_group_order_invoices_path(order), class: 'btn btn-small' diff --git a/app/views/group_order_invoices/create.js.erb b/app/views/group_order_invoices/create.js.erb new file mode 100644 index 00000000..5a43e85d --- /dev/null +++ b/app/views/group_order_invoices/create.js.erb @@ -0,0 +1 @@ +$("#generate-invoice<%= params[:id] %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>"); diff --git a/app/views/group_order_invoices/create_multiple.js.erb b/app/views/group_order_invoices/create_multiple.js.erb new file mode 100644 index 00000000..11ebbe45 --- /dev/null +++ b/app/views/group_order_invoices/create_multiple.js.erb @@ -0,0 +1 @@ +$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>"); diff --git a/app/views/group_order_invoices/destroy.js.erb b/app/views/group_order_invoices/destroy.js.erb new file mode 100644 index 00000000..30ce5985 --- /dev/null +++ b/app/views/group_order_invoices/destroy.js.erb @@ -0,0 +1 @@ +$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>"); \ No newline at end of file diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 3ffd583e..0cd27c76 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -69,7 +69,7 @@ = f.hidden_field :order_id = f.hidden_field :updated_by_user_id = f.hidden_field :ordergroup_id - %table.table.table-hover + %table.table %thead %tr %th= heading_helper Article, :name @@ -94,7 +94,7 @@ %i.icon-tag %td{colspan: "9"} - order_articles.each do |order_article| - %tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top"} + %tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top", tabindex: "0"} %td.name= order_article.article.name - if @order.stockit? %td= truncate order_article.article.supplier.name, length: 15 diff --git a/app/views/mailer/group_order_invoice.text.haml b/app/views/mailer/group_order_invoice.text.haml new file mode 100644 index 00000000..75948fbe --- /dev/null +++ b/app/views/mailer/group_order_invoice.text.haml @@ -0,0 +1 @@ += raw t '.text', group: @group.name, supplier: @supplier , foodcoop: FoodsoftConfig[:name] diff --git a/app/views/ordergroups/edit.html.haml b/app/views/ordergroups/edit.html.haml index 1cba43e6..9e964c89 100644 --- a/app/views/ordergroups/edit.html.haml +++ b/app/views/ordergroups/edit.html.haml @@ -57,6 +57,10 @@ = f.label :contact_person %br/ = f.text_field :contact_person + %p + = f.label :customer_number + %br/ + = f.text_field :customer_number %p = f.label :contact_phone %br/ diff --git a/app/views/shared/_group.html.haml b/app/views/shared/_group.html.haml index 3386aaab..c4d00679 100644 --- a/app/views/shared/_group.html.haml +++ b/app/views/shared/_group.html.haml @@ -6,6 +6,8 @@ %dd=h group.contact %dt= heading_helper(Ordergroup, :contact_address) + ':' %dd= link_to_gmaps group.contact_address + %dt= heading_helper(Ordergroup, :customer_number) + ':' + %dd=h group.customer_number - if group.break_start? or group.break_end? %dt= heading_helper(Ordergroup, :break) + ':' %dd= raw t '.break', start: format_date(group.break_start), end: format_date(group.break_end) diff --git a/config/locales/de.yml b/config/locales/de.yml index 6a957ec2..960ddf53 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -90,6 +90,17 @@ de: tolerance: Toleranz total_price: Summe unit_price: Preis/Einheit + group_order_invoice: + name: Bestellgruppenrechnung + links: + delete: Rechnung löschen + download: Rechnung herunterladen + generate: Rechnung erzeugen + invoice_date: Datum der Bestellgruppenrechnung + generate_with_date: setzen & erzeugen + download_all_zip: Alle Rechnungen herunterladen (zip) + payment_method: Guthaben + tax_number_not_set: Steuernummer in den Einstellungen nicht gesetzt invoice: amount: Betrag attachment: Anhang @@ -158,6 +169,7 @@ de: contact_address: Adresse contact_person: Kontaktperson contact_phone: Telefon + customer_number: Kundennummer description: Beschreibung ignore_apple_restriction: Bestellstop bei zu wenig Äpfeln ignorieren last_order: Zuletzt bestellt @@ -318,6 +330,7 @@ de: emails_title: E-Mails versenden tab_payment: schedule_title: Bestellschema + group_order_invoices: Bestellgruppenrechnungen tab_security: default_roles_title: Zugriff auf default_roles_paragraph: Jedes Mitglied der Foodcoop hat automatisch Zugriff auf folgende Bereiche. @@ -603,6 +616,10 @@ de: email_from: E-Mails werden so aussehen, als ob sie von dieser Adresse gesendet wurden. Kann leer gelassen werden, um die Kontaktadresse der Foodcoop zu benutzen. email_replyto: Setze diese Adresse, wenn Du Antworten auf Foodsoft E-Mails auf eine andere, als die oben angegebene Absenderadresse bekommen möchtest. email_sender: E-Mails werden so aussehen, als ob sie von dieser Adresse versendet wurden. Um zu vermeiden, dass E-Mails dadurch als Spam eingeordnet werden, muss der Webserver möglicherweise im SPF Eintrag der Domain der E-Mail Adresse eingetragen werden. + group_order_invoices: + use_automativ_go_invoices: Es werden auf die Bestellgruppen zugeschnittene Rechnungen für die jeweilige Bestellung beim Klicken auf "abrechnen" an alle Bestellgruppenmitglieder per Mail versendet. + payment_method: Zahlungsart wird auf der Bestellgruppenrechnung deklariert + vat_exempt: Eine Auflistung der Rechnungsartikel erfolgt ohne explizite Ausweisung der MwSt. und die Rechnung erhält den notwendigen Zusatz bzgl. der Kleinunternehmerregelung §19 (FoodCoop Marge ebenfalls nicht in Rechnung enthalten) help_url: Link zur Dokumentationsseite homepage: Webseite der Foodcoop ignore_browser_locale: Ignoriere die Sprache des Computers des Anwenders, wenn der Anwender noch keine Sprache gewählt hat. @@ -645,6 +662,7 @@ de: phone: Telefon street: Straße zip_code: Postleitzahl + tax_number: Steuernummer currency_space: Leerzeichen hinzufügen currency_unit: Währung custom_css: Angepasstes CSS @@ -660,6 +678,11 @@ de: email_from: Absenderadresse email_replyto: Antwortadresse email_sender: Senderadresse + group_order_invoices: + use_automatic_invoices: Automatisch bei Abrechnung per Mail versenden + separate_deposits: Pfand getrennt abrechnen + payment_method: Zahlungsart + vat_exempt: Diese Foodcoop ist MwSt. befreit help_url: URL Dokumentation homepage: Webseite ignore_browser_locale: Browsersprache ignorieren @@ -746,6 +769,47 @@ de: update: notice: Lieferung wurde aktualisiert. documents: + group_order_invoice_pdf: + filename: Rechnung%{number} + invoicer: Rechnungsteller*in + invoicee: Rechnungsempfänger*in + invoice_date: 'Rechnungsdatum: %{invoice_date}' + invoice_number: 'Rechnungsnummer: %{invoice_number}' + markup_included: zzgl. Foodcoop Marge auf brutto Preis %{marge}% + ordergroup: + contact_phone: 'Telefonnummer: %{contact_phone}' + contact_address: 'Adresse : %{contact_address}' + customer_number: 'Kundennummer: %{customer_number}' + name: Bestellgruppe %{ordergroup} + payment_method: 'Zahlungsart: %{payment_method}' + sum_to_pay: Zu zahlen gesamt + sum_to_pay_net: Zu zahlen gesamt (netto) + sum_to_pay_gross: Gesamt + small_business_regulation: Als Kleinunternehmer*in im Sinne von §19 Abs. 1 Umsatzsteuergesetz (UStG) wird keine Umsatzsteuer berechnet. + table_headline: 'Für die Bestellung fallen folgende Posten an:' + tax_excluded: exkl. MwSt. + tax_included: zzgl. Gesamtsumme MwSt. %{tax}% + tax_number: 'Steuernummer: %{number}' + title: Rechnung für die Bestellung bei %{supplier} + vat_exempt_rows: + - Name + - Anzahl + - Einzelpreis + - Artikel Gesamtpreis + no_price_markup_rows: + - Name + - Anzahl + - Einzelpreis (netto) + - Artikel Gesamtpreis (netto) + - MwSt. + - Artikel Gesamtpreis (brutto) + price_markup_rows: + - Name + - Anzahl + - Einzelpreis (netto) + - Artikel Gesamtpreis (netto) + - MwSt. + - Artikel Gesamtpreis (brutto) inkl. Foodcoopmarge %{marge}% order_by_articles: filename: Bestellung %{name}-%{date} - Artikelsortierung title: 'Artikelsortierung der Bestellung: %{name}, beendet am %{date}' @@ -769,6 +833,7 @@ de: heading: Artikelübersicht (%{count}) title: 'Sortiermatrix der Bestellung: %{name}, beendet am %{date}' errors: + check_tax_number: Überprüft, ob die Steuernummer der Foodcoop richtig gesetzt ist general: Ein Problem ist aufgetreten. general_again: Ein Fehler ist aufgetreten. Bitte erneut versuchen. general_msg: 'Ein Fehler ist aufgetreten: %{msg}' @@ -792,6 +857,8 @@ de: close: alert: 'Ein Fehler ist beim Abrechnen aufgetreten: %{message}' notice: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. + notice_mail: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. Außerdem wurden automatisch Rechnungen an die Bestellgruppenmitglieder geschickt. + settings_not_set: Keine Emails mit Bestellgruppenrechnungen versendet. Bitte überprüfe die Einstellungen. Steuernummer gesetzt? close_all_direct_with_invoice: notice: 'Es wurden %{count} Bestellung abgerechnet.' close_direct: @@ -856,11 +923,15 @@ de: ended: beendet name: Lieferantin no_closed_orders: Derzeit gibt es keine beendeten Bestellungen. + state: Status summary: changed: Daten wurden verändert! duration: von %{starts} bis %{ends} fc_amount: 'FC-Betrag:' + deposit: 'Pfand netto:' + gross_deposit: 'Pfand brutto:' + fc_deposit: 'Pfand FC-Betrag:' fc_profit: FC Gewinn gross_amount: 'Bruttobetrag:' groups_amount: 'Gruppenbeträge:' @@ -1273,6 +1344,15 @@ de: header: "%{user} schrieb am %{date}:" subject: Feedback zur Foodsoft from_via_foodsoft: "%{name} via Foodsoft" + group_order_invoice: + subject: Bestellgruppenrechnung für %{group} bei %{supplier} + text: | + Liebe Bestellgruppe %{group}, + + Die Sammelbestellung bei %{supplier} wurde soeben abgerechnet und für die jeweiligen Bestellgruppen Rechnungen angelegt. + Im Anhang befindet sich daher eure Rechnung. + + Viele Grüße von %{foodcoop} invite: subject: Einladung in die Foodcoop text: | @@ -1508,6 +1588,7 @@ de: orders_finished: Beendet orders_open: Laufend orders_settled: Abgerechnet + not_closed: Bestellung noch nicht abgerechnet title: Bestellungen verwalten model: close_direct_message: Die Bestellung wurde abgechlossen, ohne die Mitgliederkonten zu belasten. @@ -1907,3 +1988,4 @@ de: time: formats: foodsoft_datetime: "%d.%m.%Y %H:%M" + file: "%Y-%d-%B" \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index b4f41c5c..1ee30987 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -90,6 +90,18 @@ en: tolerance: Tolerance total_price: Sum unit_price: Price/Unit + group_order_invoice: + name: Group order invoice + links: + delete: delete invoice + download: download invoice + invoice_date: date of group order invoice + generate: generate invoice + generate_with_date: set & generate + download_all_zip: download all invoices as zip + + payment_method: Credit + tax_number_not_set: Tax number not set in configs invoice: amount: Amount attachment: Attachment @@ -158,6 +170,7 @@ en: contact_address: Address contact_person: Contact person contact_phone: Phone + customer_number: Customer number description: Description ignore_apple_restriction: Ignore order stop by apple points restriction last_order: Last order @@ -318,6 +331,7 @@ en: emails_title: Sending email tab_payment: schedule_title: Ordering schedule + group_order_invoices: Group order invoices tab_security: default_roles_title: Access to default_roles_paragraph: By default every member of the foodcoop has access to the following areas. @@ -603,6 +617,9 @@ en: email_from: Emails will appear to be from this email address. Leave empty to use the foodcoop's contact address. email_replyto: Set this when you want to receive replies from emails sent by Foodsoft on a different address than the above. email_sender: Emails will appear to be sent from this email address. To avoid emails sent being classified as spam, the webserver may need to be registered in the SPF record of the email address's domain. + use_automatic_invoices: A listing of the invoice items is made without explicit display of VAT and the invoice receives the necessary addition regarding the small business regulation §19 (applies to Germany) + payment_method: Payment type is declared on the order group invoice + vat_exempt: A listing of the invoice items is made without explicit display of VAT and the invoice contains the necessary addition regarding the German Kleinunternehmerregelung §19 UStG (attention! FoodCoop marge not included in nvoice). help_url: Documentation website. homepage: Website of your foodcoop. ignore_browser_locale: Ignore the language of user's computer when the user has not chosen a language yet. @@ -630,6 +647,8 @@ en: tolerance_is_costly: Order as much of the member tolerance as possible (compared to only as much needed to fill the last box). Enabling this also includes the tolerance in the total price of the open member order. distribution_strategy: How articles should be distributed after an order has been received. use_apple_points: When the apple point system is enabled, members are required to do some tasks to be able to keep ordering. + use_automatic_invoices: When an order is settled, invoices for the individual order groups are automatically sent by mail + payment_method: Payment Method for group order invoices use_boxfill: When enabled, near end of an order, members are only able to change their order when increases the total amount ordered. This helps to fill any remaining boxes. You still need to set a box-fill date for the orders. use_iban: When enabled, supplier and user provide an additonal field for storing the international bank account number. use_nick: Show and use nicknames instead of real names. When enabling this, please check that each user has a nickname. @@ -645,6 +664,7 @@ en: phone: Phone street: Street zip_code: Postcode + tax_number: Tax number currency_space: add space currency_unit: Currency custom_css: Custom CSS @@ -689,6 +709,11 @@ en: first_order_first_serve: First distribute to those who ordered first no_automatic_distribution: No automatic distribution use_apple_points: Apple points + group_order_invoices: + use_automatic_invoices: Send automatically via mail after oder settlement + payment_method: Payment method + separate_deposits: Separate deposits on invoice + vat_exempt: This foodcoopis VAT exempt use_boxfill: Box-fill phase use_iban: Use IBAN use_nick: Use nicknames @@ -746,6 +771,47 @@ en: update: notice: Delivery was updated. documents: + group_order_invoice_pdf: + ordergroup: + contact_phone: 'Phone: %{contact_phone}' + contact_address: 'Adress : %{contact_address}' + customer_number: 'Customer number: %{customer_number}' + name: 'Ordergroup: %{ordergroup}' + filename: Invoice%{number} + invoicee: Invoicee + invoicer: Invoicer + invoice_date: 'Invoice date: %{invoice_date}' + invoice_number: 'Invoice number: %{invoice_number}' + markup_included: incl Foodcoop Marge on gross price %{marge}% + payment_method: 'Payment_method: %{payment_method}' + small_business_regulation: As a small entrepreneur in the sense of §19 para. 1 of the Umsatzsteuergesetz (UStG), no value added tax is charged. + sum_to_pay: Total sum + sum_to_pay_net: Total sum (net) + sum_to_pay_gross: Total sum (gross) + table_headline: 'The following items will be charged for the order:' + tax_excluded: excl. MwSt. + tax_included: incl. VAT %{tax}% + tax_number: 'Tax number: %{number}' + title: Invoice for order at %{supplier} + vat_exempt_rows: + - Name + - Quantity + - Unit price + - Total price + no_price_markup_rows: + - Name + - Quantity + - Unit price (net) + - Total price (net) + - VAT + - Total price (gross) + price_markup_rows: + - Name + - Quantity + - Unit price (net) + - Total price (net) + - VAT + - Total price (gross) incl. foodcoop margin order_by_articles: filename: Order %{name}-%{date} - by articles title: 'Order sorted by articles: %{name}, closed at %{date}' @@ -769,6 +835,7 @@ en: heading: Article overview (%{count}) title: 'Order sorting matrix: %{name}, closed at %{date}' errors: + check_tax_number: Please check whether the foodcoop's tax number is set correctly. general: A problem has occured. general_again: A problem has occured. Please try again. general_msg: 'A problem has occured: %{msg}' @@ -792,6 +859,7 @@ en: close: alert: 'An error occured while accounting: %{message}' notice: Order was settled succesfully, the balance of the account was updated. + settings_not_set: No emails with order group invoices sent. Please check the settings. Tax number set? close_all_direct_with_invoice: notice: '%{count} orders have been settled.' close_direct: @@ -1272,6 +1340,15 @@ en: feedback: header: "%{user} wrote at %{date}:" subject: Feedback for Foodsoft + group_order_invoice: + subject: Order group invoice for %{group} at %{supplier} + text: | + Dear order group %{group}, + + The collective order at %{supplier} has just been settled and invoices have been created for the respective order groups. + Attached you will find your invoice. + + Best regards from %{foodcoop} from_via_foodsoft: "%{name} via Foodsoft" invite: subject: Invitation to the Foodcoop @@ -1511,6 +1588,7 @@ en: orders_finished: Closed orders_open: Open orders_settled: Settled + not_closed: Order not yet settled title: Manage orders model: close_direct_message: Order settled without charging member accounts. @@ -1910,3 +1988,4 @@ en: time: formats: foodsoft_datetime: "%Y-%m-%d %H:%M" + file: "%Y-%d-%B" diff --git a/config/routes.rb b/config/routes.rb index 8fea34b0..de68ce73 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -143,6 +143,13 @@ Rails.application.routes.draw do end end + + post 'finance/group_order_invoice', to: 'group_order_invoices#create_multiple' + + get 'orders/:order_id/group_order_invoices/download_all', to: 'group_order_invoices#download_all', as: 'download_all_group_order_invoices' + + resources :group_order_invoices + resources :article_categories ########### Finance diff --git a/db/migrate/20211208142719_create_group_order_invoices.rb b/db/migrate/20211208142719_create_group_order_invoices.rb new file mode 100644 index 00000000..b0aa13f7 --- /dev/null +++ b/db/migrate/20211208142719_create_group_order_invoices.rb @@ -0,0 +1,13 @@ +class CreateGroupOrderInvoices < ActiveRecord::Migration[5.2] + def change + create_table :group_order_invoices do |t| + t.integer :group_order_id + t.bigint :invoice_number, unique: true, limit: 8 + t.date :invoice_date + t.string :payment_method + + t.timestamps + end + add_index :group_order_invoices, :group_order_id, unique: true + end +end diff --git a/db/migrate/20230822120005_add_customer_number_to_group.rb b/db/migrate/20230822120005_add_customer_number_to_group.rb new file mode 100644 index 00000000..9b4c2278 --- /dev/null +++ b/db/migrate/20230822120005_add_customer_number_to_group.rb @@ -0,0 +1,5 @@ +class AddCustomerNumberToGroup < ActiveRecord::Migration[7.0] + def change + add_column :groups, :customer_number, :string, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 4c853039..e024426f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,8 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do - create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| +ActiveRecord::Schema[7.0].define(version: 2023_08_22_120005) do + create_table "action_text_rich_texts", charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.text "body", size: :long t.string "record_type", null: false @@ -21,7 +21,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true end - create_table "active_storage_attachments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "active_storage_attachments", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false t.bigint "record_id", null: false @@ -31,7 +31,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end - create_table "active_storage_blobs", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "active_storage_blobs", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "key", null: false t.string "filename", null: false t.string "content_type" @@ -43,19 +43,19 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end - create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "active_storage_variant_records", charset: "utf8mb4", force: :cascade do |t| t.integer "blob_id", null: false t.string "variation_digest", null: false t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end - create_table "article_categories", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "article_categories", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", default: "", null: false t.string "description" t.index ["name"], name: "index_article_categories_on_name", unique: true end - create_table "article_prices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "article_prices", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "article_id", null: false t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false t.decimal "tax", precision: 8, scale: 2, default: "0.0", null: false @@ -65,7 +65,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["article_id"], name: "index_article_prices_on_article_id" end - create_table "articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "articles", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", default: "", null: false t.integer "supplier_id", default: 0, null: false t.integer "article_category_id", default: 0, null: false @@ -91,14 +91,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["type"], name: "index_articles_on_type" end - create_table "assignments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "assignments", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "user_id", default: 0, null: false t.integer "task_id", default: 0, null: false t.boolean "accepted", default: false t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true end - create_table "bank_accounts", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "bank_accounts", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "iban" t.string "description" @@ -108,14 +108,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.integer "bank_gateway_id" end - create_table "bank_gateways", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "bank_gateways", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "url", null: false t.string "authorization" t.integer "unattended_user_id" end - create_table "bank_transactions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "bank_transactions", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "bank_account_id", null: false t.string "external_id" t.date "date" @@ -129,7 +129,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id" end - create_table "documents", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "documents", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name" t.string "mime" t.binary "data", size: :long @@ -140,16 +140,16 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["parent_id"], name: "index_documents_on_parent_id" end - create_table "financial_links", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "financial_links", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.text "note" end - create_table "financial_transaction_classes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "financial_transaction_classes", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.boolean "ignore_for_account_balance", default: false, null: false end - create_table "financial_transaction_types", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "financial_transaction_types", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.integer "financial_transaction_class_id", null: false t.string "name_short" @@ -157,7 +157,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["name_short"], name: "index_financial_transaction_types_on_name_short" end - create_table "financial_transactions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "financial_transactions", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "ordergroup_id" t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false t.text "note", null: false @@ -171,7 +171,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["reverts_id"], name: "index_financial_transactions_on_reverts_id", unique: true end - create_table "group_order_article_quantities", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "group_order_article_quantities", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "group_order_article_id", default: 0, null: false t.integer "quantity", default: 0 t.integer "tolerance", default: 0 @@ -179,7 +179,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["group_order_article_id"], name: "index_group_order_article_quantities_on_group_order_article_id" end - create_table "group_order_articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "group_order_articles", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "group_order_id", default: 0, null: false t.integer "order_article_id", default: 0, null: false t.integer "quantity", default: 0, null: false @@ -192,7 +192,17 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id" end - create_table "group_orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "group_order_invoices", charset: "utf8mb4", force: :cascade do |t| + t.integer "group_order_id" + t.bigint "invoice_number" + t.date "invoice_date" + t.string "payment_method" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["group_order_id"], name: "index_group_order_invoices_on_group_order_id", unique: true + end + + create_table "group_orders", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "ordergroup_id" t.integer "order_id", default: 0, null: false t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false @@ -205,7 +215,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["ordergroup_id"], name: "index_group_orders_on_ordergroup_id" end - create_table "groups", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "groups", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "type", default: "", null: false t.string "name", default: "", null: false t.string "description" @@ -227,10 +237,11 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.date "break_end" t.boolean "role_invoices", default: false, null: false t.boolean "role_pickups", default: false, null: false + t.string "customer_number" t.index ["name"], name: "index_groups_on_name", unique: true end - create_table "invites", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "invites", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "token", default: "", null: false t.datetime "expires_at", precision: nil, null: false t.integer "group_id", default: 0, null: false @@ -239,7 +250,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["token"], name: "index_invites_on_token" end - create_table "invoices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "invoices", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "supplier_id" t.string "number" t.date "date" @@ -257,7 +268,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["supplier_id"], name: "index_invoices_on_supplier_id" end - create_table "links", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "links", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "url", null: false t.integer "workgroup_id" @@ -265,7 +276,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.string "authorization" end - create_table "mail_delivery_status", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "mail_delivery_status", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.datetime "created_at", precision: nil t.string "email", null: false t.string "message", null: false @@ -274,13 +285,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["email"], name: "index_mail_delivery_status_on_email" end - create_table "memberships", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "memberships", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "group_id", default: 0, null: false t.integer "user_id", default: 0, null: false t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true end - create_table "message_recipients", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "message_recipients", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "message_id", null: false t.integer "user_id", null: false t.integer "email_state", default: 0, null: false @@ -289,7 +300,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at" end - create_table "messages", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "messages", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "sender_id" t.string "subject", null: false t.boolean "private", default: false @@ -300,7 +311,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.binary "received_email", size: :medium end - create_table "oauth_access_grants", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "oauth_access_grants", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "resource_owner_id", null: false t.integer "application_id", null: false t.string "token", null: false @@ -312,7 +323,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true end - create_table "oauth_access_tokens", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "oauth_access_tokens", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "resource_owner_id" t.integer "application_id" t.string "token", null: false @@ -326,7 +337,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true end - create_table "oauth_applications", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "oauth_applications", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "uid", null: false t.string "secret", null: false @@ -338,7 +349,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true end - create_table "order_articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "order_articles", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "order_id", default: 0, null: false t.integer "article_id", default: 0, null: false t.integer "quantity", default: 0, null: false @@ -352,7 +363,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["order_id"], name: "index_order_articles_on_order_id" end - create_table "order_comments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "order_comments", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "order_id" t.integer "user_id" t.text "text" @@ -360,7 +371,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["order_id"], name: "index_order_comments_on_order_id" end - create_table "orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "orders", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "supplier_id" t.text "note" t.datetime "starts", precision: nil @@ -379,7 +390,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["state"], name: "index_orders_on_state" end - create_table "page_versions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "page_versions", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "page_id" t.integer "lock_version" t.text "body" @@ -390,7 +401,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["page_id"], name: "index_page_versions_on_page_id" end - create_table "pages", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "pages", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "title" t.text "body" t.string "permalink" @@ -404,20 +415,20 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["title"], name: "index_pages_on_title" end - create_table "periodic_task_groups", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "periodic_task_groups", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.date "next_task_date" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "poll_choices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "poll_choices", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "poll_vote_id", null: false t.integer "choice", null: false t.integer "value", null: false t.index ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true end - create_table "poll_votes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "poll_votes", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "poll_id", null: false t.integer "user_id", null: false t.integer "ordergroup_id" @@ -427,7 +438,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true end - create_table "polls", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "polls", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "created_by_user_id", null: false t.string "name", null: false t.text "description" @@ -447,7 +458,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["final_choice"], name: "index_polls_on_final_choice" end - create_table "printer_job_updates", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "printer_job_updates", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "printer_job_id", null: false t.datetime "created_at", precision: nil, null: false t.string "state", null: false @@ -455,7 +466,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at" end - create_table "printer_jobs", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "printer_jobs", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "order_id" t.string "document", null: false t.integer "created_by_user_id", null: false @@ -464,7 +475,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["finished_at"], name: "index_printer_jobs_on_finished_at" end - create_table "settings", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "settings", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "var", null: false t.text "value" t.integer "thing_id" @@ -474,7 +485,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true end - create_table "stock_changes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "stock_changes", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "stock_event_id" t.integer "order_id" t.integer "stock_article_id" @@ -484,7 +495,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["stock_event_id"], name: "index_stock_changes_on_stock_event_id" end - create_table "stock_events", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "stock_events", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.integer "supplier_id" t.date "date" t.datetime "created_at", precision: nil @@ -494,14 +505,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["supplier_id"], name: "index_stock_events_on_supplier_id" end - create_table "supplier_categories", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "supplier_categories", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "description" t.integer "financial_transaction_class_id" t.integer "bank_account_id" end - create_table "suppliers", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "suppliers", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", default: "", null: false t.string "address", default: "", null: false t.string "phone", default: "", null: false @@ -523,7 +534,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["name"], name: "index_suppliers_on_name", unique: true end - create_table "tasks", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "tasks", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "name", default: "", null: false t.text "description" t.date "due_date" @@ -540,7 +551,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do t.index ["workgroup_id"], name: "index_tasks_on_workgroup_id" end - create_table "users", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "users", id: :integer, charset: "utf8mb4", force: :cascade do |t| t.string "nick" t.string "password_hash", default: "", null: false t.string "password_salt", default: "", null: false diff --git a/spec/factories/group_order_invoice.rb b/spec/factories/group_order_invoice.rb new file mode 100644 index 00000000..89723873 --- /dev/null +++ b/spec/factories/group_order_invoice.rb @@ -0,0 +1,7 @@ +require 'factory_bot' + +FactoryBot.define do + factory :group_order_invoice do + group_order { create :group_order } + end +end diff --git a/spec/integration/group_order_invoices_spec.rb b/spec/integration/group_order_invoices_spec.rb new file mode 100644 index 00000000..f6ece77d --- /dev/null +++ b/spec/integration/group_order_invoices_spec.rb @@ -0,0 +1,72 @@ +require_relative '../spec_helper' + +feature GroupOrderInvoice, js: true do + let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] } + let(:user) { create :user, groups: [create(:ordergroup)] } + let(:article) { create :article, unit_quantity: 1 } + let(:order) { create :order, supplier: article.supplier, article_ids: [article.id], ends: Time.now } # need to ref article + let(:go) { create :group_order, order: order, ordergroup: user.ordergroup} + let(:oa) { order.order_articles.find_by_article_id(article.id) } + let(:ftt) { create :financial_transaction_type } + let(:goa) { create :group_order_article, group_order: go, order_article: oa } + + include ActiveJob::TestHelper + + before { login admin } + + after { clear_enqueued_jobs } + + it 'does not enqueue MailerJob when order is settled if tax_number or options not set' do + goa.update_quantities 2, 0 + oa.update_results! + visit confirm_finance_order_path(id: order.id) + click_link_or_button I18n.t('finance.balancing.confirm.clear') + expect(NotifyGroupOrderInvoiceJob).not_to have_been_enqueued + end + + it 'enqueues MailerJob when order is settled if tax_number or options are set' do + goa.update_quantities 2, 0 + oa.update_results! + order.reload + FoodsoftConfig[:group_order_invoices] = { use_automatic_invoices: true } + FoodsoftConfig[:contact][:tax_number] = 12_345_678 + visit confirm_finance_order_path(id: order.id, type: ftt) + expect(page).to have_selector(:link_or_button, I18n.t('finance.balancing.confirm.clear')) + click_link_or_button I18n.t('finance.balancing.confirm.clear') + expect(NotifyGroupOrderInvoiceJob).to have_been_enqueued + end + + it 'generates Group Order Invoice when order is closed if tax_number is set' do + goa.update_quantities 2, 0 + oa.update_results! + FoodsoftConfig[:contact][:tax_number] = 12_345_678 + order.update!(state: 'closed') + go.reload + order.reload + visit finance_order_index_path + expect(page).to have_selector(:link_or_button, I18n.t('activerecord.attributes.group_order_invoice.links.generate')) + click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate') + expect(GroupOrderInvoice.all.count).to eq(1) + end + + it 'generates multiple Group Order Invoice for order when order is closed if tax_number is set' do + goa.update_quantities 2, 0 + oa.update_results! + FoodsoftConfig[:contact][:tax_number] = 12_345_678 + order.update!(state: 'closed') + order.reload + visit finance_order_index_path + expect(page).to have_selector(:link_or_button, I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date')) + click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date') + expect(GroupOrderInvoice.all.count).to eq(1) + end + + it 'does not generate Group Order Invoice when order is closed if tax_number not set' do + goa.update_quantities 2, 0 + oa.update_results! + order.update!(state: 'closed') + order.reload + visit finance_order_index_path + expect(page).to have_content(I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set')) + end +end diff --git a/spec/models/group_order_invoice_spec.rb b/spec/models/group_order_invoice_spec.rb new file mode 100644 index 00000000..24bfcf7e --- /dev/null +++ b/spec/models/group_order_invoice_spec.rb @@ -0,0 +1,59 @@ +require_relative '../spec_helper' + +describe GroupOrderInvoice do + let(:user) { create :user, groups: [create(:ordergroup)] } + let(:supplier) { create :supplier } + let(:article) { create :article, supplier: supplier } + let(:order) { create :order } + let(:group_order) { create :group_order, order: order, ordergroup: user.ordergroup } + + describe 'erroneous group order invoice' do + let(:goi) { create :group_order_invoice, group_order_id: group_order.id } + it 'does not create group order invoice if tax_number not set' do + expect { goi }.to raise_error(ActiveRecord::RecordInvalid, /.*/) + end + end + + describe 'valid group order invoice' do + before do + FoodsoftConfig[:contact][:tax_number] = 123_457_8 + end + + invoice_number1 = Time.now.strftime("%Y%m%d") + '0001' + invoice_number2 = Time.now.strftime("%Y%m%d") + '0002' + + let(:user2) { create :user, groups: [create(:ordergroup)] } + + let(:goi1) { create :group_order_invoice, group_order_id: group_order.id } + let(:goi2) { create :group_order_invoice, group_order_id: group_order.id } + + let(:group_order2) { create :group_order, order: order, ordergroup: user2.ordergroup } + + let(:goi3) { create :group_order_invoice, group_order_id: group_order2.id } + let(:goi4) { create :group_order_invoice, group_order_id: group_order2.id, invoice_number: invoice_number1 } + + it 'creates group order invoice if tax_number is set' do + expect(goi1).to be_valid + end + + it 'sets invoice_number according to date' do + number = Time.now.strftime("%Y%m%d") + '0001' + expect(goi1.invoice_number).to eq(number.to_i) + end + + it 'fails to create if group_order_id is used multiple times for creation' do + expect(goi1.group_order.id).to eq(group_order.id) + expect { goi2 }.to raise_error(ActiveRecord::RecordNotUnique) + end + + it 'creates two different group order invoice with different invoice_numbers' do + expect(goi1.invoice_number).to eq(invoice_number1.to_i) + expect(goi3.invoice_number).to eq(invoice_number2.to_i) + end + + it 'fails to create two different group order invoice with same invoice_numbers' do + goi1 + expect { goi4 }.to raise_error(ActiveRecord::RecordInvalid) + end + end +end