diff --git a/app/assets/images/label_template_icons/fluics.svg b/app/assets/images/label_template_icons/fluics.svg new file mode 100644 index 000000000..2f19db0b3 --- /dev/null +++ b/app/assets/images/label_template_icons/fluics.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/assets/javascripts/label_templates/label_templates_datatable.js b/app/assets/javascripts/label_templates/label_templates_datatable.js index 1446ef747..23f376f7e 100644 --- a/app/assets/javascripts/label_templates/label_templates_datatable.js +++ b/app/assets/javascripts/label_templates/label_templates_datatable.js @@ -23,7 +23,7 @@ } function renderNameHTML(data, type, row) { - return `${data.icon_url} record.id, '1' => record.default, - '2' => append_format_icon(sanitize_input(record.name)), - '3' => sanitize_input(record.format), + '2' => append_format_icon(record), + '3' => sanitize_input(record.label_format), '4' => sanitize_input(record.description), '5' => sanitize_input(record.modified_by), '6' => I18n.l(record.updated_at, format: :full), @@ -52,9 +52,15 @@ class LabelTemplateDatatable < CustomDatatable end end - def append_format_icon(data) - { icon_url: ActionController::Base.helpers.image_tag('label_template_icons/zpl.svg', class: 'label-template-icon'), - name: data } + def append_format_icon(record) + { + icon_image_tag: + ActionController::Base.helpers.image_tag( + "label_template_icons/#{record.icon}.svg", + class: 'label-template-icon' + ), + name: sanitize_input(record.name) + } end def get_raw_records @@ -67,12 +73,16 @@ class LabelTemplateDatatable < CustomDatatable ).select('label_templates.* AS label_templates') .select('creators.full_name AS created_by_user') .select('modifiers.full_name AS modified_by') + .select( + "('#{Extends::LABEL_TEMPLATE_FORMAT_MAP.to_json}'::jsonb -> label_templates.type)::text "\ + "AS label_format" + ) LabelTemplate.from(res, :label_templates) end def filter_records(records) records.where_attributes_like( - ['label_templates.name', 'label_templates.format', 'label_templates.description', + ['label_templates.name', 'label_templates.label_format', 'label_templates.description', 'label_templates.modified_by', 'label_templates.created_by_user'], dt_params.dig(:search, :value) ) diff --git a/app/models/fluics_label_template.rb b/app/models/fluics_label_template.rb new file mode 100644 index 000000000..64a39c48e --- /dev/null +++ b/app/models/fluics_label_template.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class FluicsLabelTemplate < LabelTemplate + def self.default + FluicsLabelTemplate.new( + name: I18n.t('label_templates.default_fluics_name'), + width_mm: 25.4, + height_mm: 12.7, + content: Extends::DEFAULT_LABEL_TEMPLATE[:zpl] + ) + end + + def label_format + 'Fluics' + end + + def created_by_user + 'Fluics GmbH' + end + + def modified_by + 'Fluics GmbH' + end + + def icon + 'fluics' + end + + def read_only? + true + end +end diff --git a/app/models/label_template.rb b/app/models/label_template.rb index e2b140c29..7a0c1bd9f 100644 --- a/app/models/label_template.rb +++ b/app/models/label_template.rb @@ -3,13 +3,13 @@ class LabelTemplate < ApplicationRecord include SearchableModel - enum language_type: { zpl: 0 } + belongs_to :team + validates :name, presence: true, length: { minimum: Constants::NAME_MIN_LENGTH, maximum: Constants::NAME_MAX_LENGTH } - validates :size, presence: true validates :content, presence: true - validate :default_template + validate :ensure_single_default_template! def self.enabled? ApplicationSettings.instance.values['label_templates_enabled'] @@ -21,18 +21,27 @@ class LabelTemplate < ApplicationRecord end end - def icon_url - case language_type - when 'zpl' - '/images/label_template_icons/zpl.svg' - end + def icon + 'zpl' + end + + def language_type + 'zpl' + end + + def read_only? + false + end + + def label_format + Extends::LABEL_TEMPLATE_FORMAT_MAP[type] end private - def default_template - if default && LabelTemplate.where(team_id: team_id, default: true, language_type: language_type) - .where.not(id: id).any? + def ensure_single_default_template! + if default && self.class.where(team_id: team_id, default: true, language_type: language_type) + .where.not(id: id).any? errors.add(:default, I18n.t('activerecord.errors.models.label_template.attributes.default.already_exist')) end end diff --git a/app/models/team.rb b/app/models/team.rb index 749af729e..9f338ff45 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -11,6 +11,7 @@ class Team < ApplicationRecord include ActionView::Helpers::NumberHelper after_create :generate_template_project + after_create :create_default_label_templates scope :teams_select, -> { select(:id, :name).order(name: :asc) } scope :ordered, -> { order('LOWER(name)') } @@ -41,6 +42,7 @@ class Team < ApplicationRecord has_many :assets, inverse_of: :team, dependent: :destroy has_many :team_repositories, inverse_of: :team, dependent: :destroy has_many :shared_repositories, through: :team_repositories, source: :repository + has_many :label_templates, dependent: :destroy attr_accessor :without_templates @@ -170,4 +172,9 @@ class Team < ApplicationRecord return if without_templates TemplatesService.new.delay(queue: :templates).update_team(self) end + + def create_default_label_templates + ZebraLabelTemplate.default.update(team: self, default: true) + FluicsLabelTemplate.default.update(team: self, default: true) + end end diff --git a/app/models/zebra_label_template.rb b/app/models/zebra_label_template.rb new file mode 100644 index 000000000..5dbdc290d --- /dev/null +++ b/app/models/zebra_label_template.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class ZebraLabelTemplate < LabelTemplate + def self.default + ZebraLabelTemplate.new( + name: I18n.t('label_templates.default_zebra_name'), + width_mm: 25.4, + height_mm: 12.7, + content: Extends::DEFAULT_LABEL_TEMPLATE[:zpl] + ) + end +end diff --git a/app/serializers/label_template_serializer.rb b/app/serializers/label_template_serializer.rb index 6b4ee7b27..279952655 100644 --- a/app/serializers/label_template_serializer.rb +++ b/app/serializers/label_template_serializer.rb @@ -5,6 +5,10 @@ class LabelTemplateSerializer < ActiveModel::Serializer attributes :name, :description, :language_type, :icon_url, :urls, :content + def icon_url + ActionController::Base.helpers.image_path("label_template_icons/#{object.icon}.svg") + end + def urls { update: label_template_path(object) diff --git a/app/services/label_templates/repository_row_service.rb b/app/services/label_templates/repository_row_service.rb new file mode 100644 index 000000000..de762d1b7 --- /dev/null +++ b/app/services/label_templates/repository_row_service.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module LabelTemplates + class RepositoryRowService + class UnsupportedKeyError < StandardError; end + + class ColumnNotFoundError < StandardError; end + + def initialize(label_template, repository_row) + @label_template = label_template + @repository_row = repository_row + end + + def render + keys = @label_template.content.scan(/(?<=\{\{).*?(?=\}\})/).uniq + + keys.reduce(@label_template.content.dup) do |rendered_content, key| + rendered_content.gsub!(/\{\{#{key}\}\}/, fetch_value(key)) + end + end + + private + + def fetch_value(key) + case key + when /^COLUMN_\[(.*)\]/ + name = Regexp.last_match(1) + repository_cell = @repository_row.repository_cells.joins(:repository_column).find_by( + repository_columns: { name: name } + ) + + unless repository_cell + raise UnsupportedKeyError, I18n.t('label_templates.repository_row.errors.column_not_found', column: name) + end + + repository_cell.value.formatted + when 'ITEM_ID' + @repository_row.code + when 'NAME' + @repository_row.name + when 'ADDED_BY' + @repository_row.created_by.full_name + when 'ADDED_ON' + @repository_row.created_at.to_s + else + raise UnsupportedKeyError, I18n.t('label_templates.repository_row.errors.unsupported_key', key: key) + end + end + end +end diff --git a/config/initializers/extends.rb b/config/initializers/extends.rb index feb043a9a..d4e56b406 100644 --- a/config/initializers/extends.rb +++ b/config/initializers/extends.rb @@ -502,12 +502,17 @@ class Extends ^LH20,20 ^PW310 ^CF0,23 - ^FO0,0^FD{{item_id}}^FS - ^FO0,20^BQN,2,4^FDMA,{{item_id}}^FS - ^FO95,30^FB180,4,0,L^FD{{item_name}}^FS^FS + ^FO0,0^FD{{ITEM_ID}}^FS + ^FO0,20^BQN,2,4^FDMA,{{ITEM_ID}}^FS + ^FO95,30^FB180,4,0,L^FD{{NAME}}^FS^FS ^XZ HEREDOC } + + LABEL_TEMPLATE_FORMAT_MAP = { + 'ZebraLabelTemplate' => 'ZPL', + 'FluicsLabelTemplate' => 'Fluics' + } end # rubocop:enable Style/MutableConstant diff --git a/config/locales/en.yml b/config/locales/en.yml index 8b7ba68ef..847d82009 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -802,6 +802,12 @@ en: normal_user: "Normal user" admin: "Administrator" + label_templates: + repository_row: + errors: + unsupported_key: "Key %{key} is not supported by this label template renderer." + column_not_found: "Column '%{column}' not found on inventory item." + user_projects: view_users: modal_title: "Members of %{name}" @@ -815,6 +821,8 @@ en: can_add_user_to_project: "Can not add user to the project." label_templates: + default_zebra_name: 'SciNote Item (ZPL)' + default_fluics_name: 'SciNote Item (Fluics)' new_label_template: 'New label' index: head_title: 'Label templates' diff --git a/db/migrate/20220530144300_fix_label_template.rb b/db/migrate/20220530144300_fix_label_template.rb index 720e2bf8f..e447db1f8 100644 --- a/db/migrate/20220530144300_fix_label_template.rb +++ b/db/migrate/20220530144300_fix_label_template.rb @@ -2,7 +2,8 @@ class FixLabelTemplate < ActiveRecord::Migration[6.1] def change - LabelTemplate.last.update( + # rubocop:disable Rails/SkipsModelValidations + LabelTemplate.last.update_columns( name: 'SciNote Item', size: '1" x 0.5" / 25.4mm x 12.7mm', language_type: :zpl, @@ -23,5 +24,6 @@ class FixLabelTemplate < ActiveRecord::Migration[6.1] ^XZ HEREDOC ) + # rubocop:enable Rails/SkipsModelValidations end end diff --git a/db/migrate/20220803122405_update_label_templates.rb b/db/migrate/20220803122405_update_label_templates.rb new file mode 100644 index 000000000..132847a3b --- /dev/null +++ b/db/migrate/20220803122405_update_label_templates.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class UpdateLabelTemplates < ActiveRecord::Migration[6.1] + def up + change_table :label_templates, bulk: true do |t| + t.string :type + t.float :width_mm + t.float :height_mm + t.remove :format + t.remove :language_type + t.remove :size + end + + # Remove our original default template + LabelTemplate.order(created_at: :asc).find_by(default: true)&.destroy + + Team.find_each { |t| t.__send__(:create_default_label_templates) } + end + + def down + change_table :label_templates, bulk: true do |t| + t.remove :type + t.remove :width_mm + t.remoe :height_mm + t.string :format, null: false, default: 'ZPL' + t.integer :language_type + t.string :size + end + end +end diff --git a/db/structure.sql b/db/structure.sql index ea7f62b76..173ce0980 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -698,11 +698,16 @@ CREATE TABLE public.label_templates ( id bigint NOT NULL, name character varying NOT NULL, content text NOT NULL, - language_type integer, "default" boolean DEFAULT false NOT NULL, - size character varying, created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL + updated_at timestamp(6) without time zone NOT NULL, + description character varying, + last_modified_by_id integer, + created_by_id integer, + team_id bigint, + type character varying, + width_mm double precision, + height_mm double precision ); @@ -5165,10 +5170,24 @@ CREATE INDEX index_hidden_repository_cell_reminders_on_user_id ON public.hidden_ -- --- Name: index_label_templates_on_language_type; Type: INDEX; Schema: public; Owner: - +-- Name: index_label_templates_on_created_by_id; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_label_templates_on_language_type ON public.label_templates USING btree (language_type); +CREATE INDEX index_label_templates_on_created_by_id ON public.label_templates USING btree (created_by_id); + + +-- +-- Name: index_label_templates_on_last_modified_by_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_label_templates_on_last_modified_by_id ON public.label_templates USING btree (last_modified_by_id); + + +-- +-- Name: index_label_templates_on_team_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_label_templates_on_team_id ON public.label_templates USING btree (team_id); -- @@ -5816,10 +5835,10 @@ CREATE INDEX index_repository_cells_on_repository_row_id ON public.repository_ce -- --- Name: index_repository_cells_on_value; Type: INDEX; Schema: public; Owner: - +-- Name: index_repository_cells_on_value_type_and_value_id; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_repository_cells_on_value ON public.repository_cells USING btree (value_type, value_id); +CREATE INDEX index_repository_cells_on_value_type_and_value_id ON public.repository_cells USING btree (value_type, value_id); -- @@ -6845,10 +6864,10 @@ CREATE INDEX index_view_states_on_user_id ON public.view_states USING btree (use -- --- Name: index_view_states_on_viewable; Type: INDEX; Schema: public; Owner: - +-- Name: index_view_states_on_viewable_type_and_viewable_id; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_view_states_on_viewable ON public.view_states USING btree (viewable_type, viewable_id); +CREATE INDEX index_view_states_on_viewable_type_and_viewable_id ON public.view_states USING btree (viewable_type, viewable_id); -- @@ -6943,6 +6962,14 @@ ALTER TABLE ONLY public.assets ADD CONSTRAINT fk_rails_0916329f9e FOREIGN KEY (created_by_id) REFERENCES public.users(id); +-- +-- Name: label_templates fk_rails_09d7cc0c34; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.label_templates + ADD CONSTRAINT fk_rails_09d7cc0c34 FOREIGN KEY (team_id) REFERENCES public.teams(id); + + -- -- Name: user_assignments fk_rails_0b13c65ab0; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -7007,6 +7034,14 @@ ALTER TABLE ONLY public.user_assignments ADD CONSTRAINT fk_rails_19dca62dfc FOREIGN KEY (user_role_id) REFERENCES public.user_roles(id); +-- +-- Name: label_templates fk_rails_1aa41d1093; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.label_templates + ADD CONSTRAINT fk_rails_1aa41d1093 FOREIGN KEY (created_by_id) REFERENCES public.users(id); + + -- -- Name: user_system_notifications fk_rails_20d9487a3c; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -8087,6 +8122,14 @@ ALTER TABLE ONLY public.experiments ADD CONSTRAINT fk_rails_d683124fa4 FOREIGN KEY (restored_by_id) REFERENCES public.users(id); +-- +-- Name: label_templates fk_rails_d6ac71e421; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.label_templates + ADD CONSTRAINT fk_rails_d6ac71e421 FOREIGN KEY (last_modified_by_id) REFERENCES public.users(id); + + -- -- Name: protocols fk_rails_d8007e2f63; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -8516,6 +8559,8 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220429083335'), ('20220530144300'), ('20220602120714'), -('20220705091621'); +('20220705091621'), +('20220726133419'), +('20220803122405');