mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-04 02:31:33 +00:00
Compare commits
1 Commits
master
...
v4.4.5-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0262dcd11b |
2
.browserslistrc
Normal file
2
.browserslistrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
defaults
|
||||||
|
IE 11
|
||||||
56
.codeclimate.yml
Normal file
56
.codeclimate.yml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
version: "2"
|
||||||
|
plugins:
|
||||||
|
rubocop:
|
||||||
|
enabled: true
|
||||||
|
channel: "rubocop-1-12"
|
||||||
|
config:
|
||||||
|
file: ".rubocop.yml"
|
||||||
|
scss-lint:
|
||||||
|
enabled: true
|
||||||
|
checks:
|
||||||
|
ImportantRule:
|
||||||
|
enabled: false
|
||||||
|
VendorPrefix:
|
||||||
|
enabled: false
|
||||||
|
LeadingZero:
|
||||||
|
enabled: false
|
||||||
|
PropertySortOrder:
|
||||||
|
enabled: false
|
||||||
|
StringQuotes:
|
||||||
|
enabled: false
|
||||||
|
DeclarationOrder:
|
||||||
|
enabled: false
|
||||||
|
NestingDepth:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
duplication:
|
||||||
|
enabled: true
|
||||||
|
exclude_patterns:
|
||||||
|
- "db/**"
|
||||||
|
- "config/initializers/active_record_postgresql_referential_integrity_patch.rb"
|
||||||
|
checks:
|
||||||
|
argument-count:
|
||||||
|
enabled: false
|
||||||
|
complex-logic:
|
||||||
|
enabled: false
|
||||||
|
file-lines:
|
||||||
|
enabled: false
|
||||||
|
method-complexity:
|
||||||
|
enabled: false
|
||||||
|
method-count:
|
||||||
|
enabled: false
|
||||||
|
method-lines:
|
||||||
|
enabled: false
|
||||||
|
nested-control-flow:
|
||||||
|
enabled: false
|
||||||
|
return-statements:
|
||||||
|
enabled: false
|
||||||
|
similar-code:
|
||||||
|
enabled: false
|
||||||
|
identical-code:
|
||||||
|
enabled: false
|
||||||
|
exclude_patterns:
|
||||||
|
- "spec/**/*"
|
||||||
|
- "vendor/**/*"
|
||||||
|
- "app/assets/javascripts/shared/*"
|
||||||
|
- "app/assets/javascripts/jquery-migrate-1.0.0.js"
|
||||||
29
.env
29
.env
@@ -10,10 +10,10 @@ TIMEZONE="Melbourne"
|
|||||||
DEFAULT_COUNTRY_CODE="AU"
|
DEFAULT_COUNTRY_CODE="AU"
|
||||||
|
|
||||||
# Locale for translation.
|
# Locale for translation.
|
||||||
LOCALE="en_AU"
|
LOCALE="en"
|
||||||
|
|
||||||
# For multilingual - ENV doesn't have array so pass it as string with commas
|
# For multilingual - ENV doesn't have array so pass it as string with commas
|
||||||
AVAILABLE_LOCALES="en_AU,es"
|
AVAILABLE_LOCALES="en,es"
|
||||||
|
|
||||||
# Spree zone.
|
# Spree zone.
|
||||||
CHECKOUT_ZONE="Australia"
|
CHECKOUT_ZONE="Australia"
|
||||||
@@ -42,10 +42,16 @@ SMTP_PASSWORD="f00d"
|
|||||||
# Javascript error reporting via Bugsnag.
|
# Javascript error reporting via Bugsnag.
|
||||||
# BUGSNAG_JS_KEY=""
|
# BUGSNAG_JS_KEY=""
|
||||||
|
|
||||||
|
# SingleSignOn login for Discourse
|
||||||
|
#
|
||||||
|
# DISCOURSE_SSO_SECRET should be a random string. It must be the same as provided to your Discourse instance.
|
||||||
|
# DISCOURSE_SSO_SECRET=""
|
||||||
|
#
|
||||||
|
# DISCOURSE_URL must be the URL of your Discourse instance.
|
||||||
|
# DISCOURSE_URL="https://noticeboard.openfoodnetwork.org.au"
|
||||||
|
|
||||||
# see="https://developers.google.com/maps/documentation/javascript/get-api-key
|
# see="https://developers.google.com/maps/documentation/javascript/get-api-key
|
||||||
# GOOGLE_MAPS_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
# GOOGLE_MAPS_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
# see https://developers.google.com/maps/documentation/javascript/localization#Region
|
|
||||||
# GOOGLE_MAPS_REGION="XX"
|
|
||||||
|
|
||||||
# Stripe details for instance account
|
# Stripe details for instance account
|
||||||
# Find these under 'Developers' -> 'API keys' in your Stripe account dashboard.
|
# Find these under 'Developers' -> 'API keys' in your Stripe account dashboard.
|
||||||
@@ -55,18 +61,3 @@ SMTP_PASSWORD="f00d"
|
|||||||
# STRIPE_INSTANCE_PUBLISHABLE_KEY="pk_test_xxxx" # This can be a test key or a live key
|
# STRIPE_INSTANCE_PUBLISHABLE_KEY="pk_test_xxxx" # This can be a test key or a live key
|
||||||
# STRIPE_CLIENT_ID="ca_xxxx" # This can be a development ID or a production ID
|
# STRIPE_CLIENT_ID="ca_xxxx" # This can be a development ID or a production ID
|
||||||
# STRIPE_ENDPOINT_SECRET="whsec_xxxx"
|
# STRIPE_ENDPOINT_SECRET="whsec_xxxx"
|
||||||
|
|
||||||
# New relic settings
|
|
||||||
# see: https://one.eu.newrelic.com/admin-portal/, Administration > API keys to get the license key
|
|
||||||
# NEW_RELIC_AGENT_ENABLED=true
|
|
||||||
# NEW_RELIC_APP_NAME="Open Food Network"
|
|
||||||
# NEW_RELIC_LICENSE_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
|
|
||||||
# Database encryption configuration, required for VINE connected app
|
|
||||||
# Generate with bin/rails db:encryption:init
|
|
||||||
# ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
# ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
# ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
|
|
||||||
# VINE API settings
|
|
||||||
# VINE_API_URL="https://vine-staging.openfoodnetwork.org.au/api/v1"
|
|
||||||
|
|||||||
@@ -5,28 +5,17 @@
|
|||||||
#
|
#
|
||||||
# cp .env.development .env.local
|
# cp .env.development .env.local
|
||||||
|
|
||||||
# Locale for translation. Using a locale other than `en` tests the
|
|
||||||
# successful fallback to `en`. To see up-to-date text used in production,
|
|
||||||
# set another locale in a local env file.
|
|
||||||
LOCALE="en_TST"
|
|
||||||
|
|
||||||
VERBOSE_QUERY_LOGS=true
|
VERBOSE_QUERY_LOGS=true
|
||||||
|
|
||||||
SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
|
||||||
OFN_REDIS_URL="redis://localhost:6379/1"
|
OFN_REDIS_URL="redis://localhost:6379/1"
|
||||||
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
|
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
|
||||||
OFN_REDIS_CABLE_URL="redis://localhost:6379/0"
|
|
||||||
|
|
||||||
SITE_URL="localhost:3000"
|
SITE_URL="0.0.0.0:3000"
|
||||||
|
|
||||||
# Deactivate rack-timeout in development.
|
# Deactivate rack-timeout in development.
|
||||||
# https://github.com/zombocom/rack-timeout#configuring
|
# https://github.com/zombocom/rack-timeout#configuring
|
||||||
RACK_TIMEOUT_SERVICE_TIMEOUT="0"
|
RACK_TIMEOUT_SERVICE_TIMEOUT="0"
|
||||||
RACK_TIMEOUT_WAIT_TIMEOUT="0"
|
RACK_TIMEOUT_WAIT_TIMEOUT="0"
|
||||||
RACK_TIMEOUT_WAIT_OVERTIME="0"
|
RACK_TIMEOUT_WAIT_OVERTIME="0"
|
||||||
|
|
||||||
# Database encryption configuration, required for VINE connected app
|
|
||||||
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="dev_primary_key"
|
|
||||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="dev_determinnistic_key"
|
|
||||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="dev_derivation_salt"
|
|
||||||
|
|||||||
29
.env.test
29
.env.test
@@ -1,36 +1,11 @@
|
|||||||
# ENV vars for the test environment
|
# ENV vars for the test environment
|
||||||
# Override locally with `.env.test.local`
|
# Override locally with `.env.test.local`
|
||||||
|
|
||||||
# Test env specific variables
|
|
||||||
#
|
|
||||||
# Adjust this to your computer. When you start test-driven development, you may
|
|
||||||
# want to reduce this value to avoid waiting for a test that you expect to fail.
|
|
||||||
CAPYBARA_MAX_WAIT_TIME="10"
|
|
||||||
|
|
||||||
# General app specific variables
|
|
||||||
|
|
||||||
# Locale for translation. Using a locale other than `en` tests the
|
|
||||||
# successful fallback to `en`.
|
|
||||||
LOCALE="en_TST"
|
|
||||||
|
|
||||||
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
|
|
||||||
|
|
||||||
SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
STRIPE_INSTANCE_SECRET_KEY="bogus_key"
|
STRIPE_SECRET_TEST_API_KEY="bogus_key"
|
||||||
STRIPE_CUSTOMER="bogus_customer"
|
STRIPE_CUSTOMER="bogus_customer"
|
||||||
STRIPE_ACCOUNT="bogus_account"
|
|
||||||
STRIPE_CLIENT_ID="bogus_client_id"
|
|
||||||
|
|
||||||
SITE_URL="test.host"
|
SITE_URL="test.host"
|
||||||
|
|
||||||
# OIDC Settings for DFC authentication
|
|
||||||
# Find secrets in BitWarden.
|
|
||||||
# To get a refresh token: log into the OIDC provider, connect your OFN user to it at /admin/oidc_settings, then copy the token from the database:
|
|
||||||
# ./bin/rails runner 'puts "OPENID_REFRESH_TOKEN=\"#{OidcAccount.last.refresh_token}\""'
|
|
||||||
OPENID_APP_ID="test-provider"
|
OPENID_APP_ID="test-provider"
|
||||||
OPENID_APP_SECRET="dummy-openid-app-secret-token"
|
OPENID_APP_SECRET="12345"
|
||||||
OPENID_REFRESH_TOKEN="dummy-refresh-token"
|
|
||||||
|
|
||||||
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="test_primary_key"
|
|
||||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="test_deterministic_key"
|
|
||||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="test_derivation_salt"
|
|
||||||
|
|||||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -8,3 +8,4 @@
|
|||||||
# Same thing for following files, but they don't have an sh extension
|
# Same thing for following files, but they don't have an sh extension
|
||||||
pre-commit eol=lf
|
pre-commit eol=lf
|
||||||
webpack-dev-server eol=lf
|
webpack-dev-server eol=lf
|
||||||
|
install-bundler eol=lf
|
||||||
39
.github/ISSUE_TEMPLATE/release.md
vendored
39
.github/ISSUE_TEMPLATE/release.md
vendored
@@ -7,34 +7,21 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Drafting on Friday
|
## 1. Preparation on Thursday
|
||||||
|
|
||||||
- [ ] Merge pull requests in the [Ready To Go] column
|
- [ ] Merge pull requests in the [Ready To Go] column
|
||||||
- [ ] Include translations: `script/release/update_locales`
|
- [ ] Include translations: `tx pull --force`
|
||||||
- You need the [Transifex Client] installed on your local dev environement to run the script.
|
|
||||||
- [ ] Increment version number: `git push upstream HEAD:refs/tags/vX.Y.Z`
|
|
||||||
Check for [minor or major breaking changes]
|
|
||||||
- Major: if server changes are required (eg. provision with ofn-install)
|
|
||||||
- Minor: larger change that is irreversible (eg. migration deleting data)
|
|
||||||
- Patch: all others. Shortcut: `script/release/tag`
|
|
||||||
- [ ] [Draft new release]. Look at previous [releases] for inspiration.
|
- [ ] [Draft new release]. Look at previous [releases] for inspiration.
|
||||||
- Select new release tag
|
- [ ] Notify [#instance-managers] of user-facing changes.
|
||||||
- _Generate release notes_ and check to ensure all items are arranged in the right category.
|
|
||||||
- [ ] Notify [#instance-managers] of user-facing :eyes:, API :warning: and experimental :construction: changes.
|
|
||||||
|
|
||||||
## 2. Testing
|
## 2. Testing
|
||||||
|
|
||||||
|
- [ ] [Find build] of the release commit and copy it below.
|
||||||
- [ ] Move this issue to Test Ready.
|
- [ ] Move this issue to Test Ready.
|
||||||
- [ ] Notify `@testers` in [#testing].
|
- [ ] Notify `@testers` in [#testing].
|
||||||
- [ ] Test build: [Deploy to Staging] with release tag.
|
- [ ] Test build: <!-- paste build link here, e.g. https://semaphore...builds/1234 -->
|
||||||
- [ ] Map is displayed correctly. Address changes are reflected in the map.
|
|
||||||
- [ ] Stripe with no authentication card: `4242424242424242` as shopper and as Admin. Order confirmation displays order as "Paid".
|
|
||||||
- [ ] Stripe with Authentication required card: `4000002760003184` as shopper and as Admin. As admin, check authorization through customer account `/account#/transactions` and email.
|
|
||||||
- [ ] Pay with Paypal.
|
|
||||||
- [ ] Order on mobile.
|
|
||||||
- [ ] Notify a deployer to deploy it.
|
|
||||||
|
|
||||||
## 3. Deployment at beginning of week
|
## 3. Finish on Tuesday
|
||||||
|
|
||||||
- [ ] Publish and notify [#global-community] (this is automatically posted with a plugin)
|
- [ ] Publish and notify [#global-community] (this is automatically posted with a plugin)
|
||||||
- [ ] Deploy the new release to all managed instances.
|
- [ ] Deploy the new release to all managed instances.
|
||||||
@@ -42,24 +29,20 @@ assignees: ''
|
|||||||
<pre>
|
<pre>
|
||||||
cd ofn-install
|
cd ofn-install
|
||||||
git pull
|
git pull
|
||||||
ansible-playbook --limit all_prod --extra-vars "git_version=vX.Y.Z" playbooks/deploy.yml
|
ansible-playbook --limit all-prod --extra-vars "git_version=vx.y.z" playbooks/deploy.yml
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
- [ ] Notify [#instance-managers]:
|
- [ ] Notify [#instance-managers]:
|
||||||
> @instance_managers The new release has been deployed.
|
> @instance_managers The new release has been deployed.
|
||||||
- [ ] [Create issue] for next release and confirm with next release drafter in [#delivery-circle].
|
- [ ] Nudge next release manager
|
||||||
|
|
||||||
The full process is described at https://github.com/openfoodfoundation/openfoodnetwork/wiki/Releasing.
|
The full process is described at https://github.com/openfoodfoundation/openfoodnetwork/wiki/Releasing.
|
||||||
|
|
||||||
[Ready To Go]: https://github.com/orgs/openfoodfoundation/projects/8?filterQuery=status%3A%22Ready+to+go+%F0%9F%9A%80%22
|
[Ready To Go]: #zenhub
|
||||||
[Transifex pull request]: https://github.com/openfoodfoundation/openfoodnetwork/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+head%3Atransifex
|
[Transifex pull request]: https://github.com/openfoodfoundation/openfoodnetwork/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+head%3Atransifex
|
||||||
[Draft new release]: https://github.com/openfoodfoundation/openfoodnetwork/releases/new?title=v+Code+Name&body=Congrats%0A%0ADescription%0A%0A
|
[Draft new release]: https://github.com/openfoodfoundation/openfoodnetwork/releases/new?tag=v&title=v+Code+Name&body=Congrats%0A%0ADescription%0A%0A%23%23+User+facing+changes+:eyes:%0A%0A%0A%23%23%23+Experimental+features+for+testing+:sunglasses:%0A%0A%0A%23%23+Technical+changes+:wrench:%0A%0A
|
||||||
[releases]: https://github.com/openfoodfoundation/openfoodnetwork/releases
|
[releases]: https://github.com/openfoodfoundation/openfoodnetwork/releases
|
||||||
[#instance-managers]: https://app.slack.com/client/T02G54U79/CG7NJ966B
|
[#instance-managers]: https://app.slack.com/client/T02G54U79/CG7NJ966B
|
||||||
[#testing]: https://openfoodnetwork.slack.com/app_redirect?channel=C02TZ6X00
|
[#testing]: https://openfoodnetwork.slack.com/app_redirect?channel=C02TZ6X00
|
||||||
[Deploy to Staging]: https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/stage.yml
|
[Find build]: https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2/branches/master
|
||||||
[#global-community]: https://app.slack.com/client/T02G54U79/C59ADD8F2
|
[#global-community]: https://app.slack.com/client/T02G54U79/C59ADD8F2
|
||||||
[Create issue]: https://github.com/openfoodfoundation/openfoodnetwork/issues/new?assignees=&labels=&projects=&template=release.md&title=Release
|
|
||||||
[#delivery-circle]: https://openfoodnetwork.slack.com/archives/C01T75H6G0Z
|
|
||||||
[Transifex Client]: https://developers.transifex.com/docs/cli
|
|
||||||
[minor or major breaking changes]: https://github.com/openfoodfoundation/openfoodnetwork/pulls?q=label%3A%22breaking+change%22%2C%22major+breaking+change%22
|
|
||||||
|
|||||||
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,4 +1,4 @@
|
|||||||
## What? Why?
|
#### What? Why?
|
||||||
|
|
||||||
- Closes # <!-- Insert issue number here. -->
|
- Closes # <!-- Insert issue number here. -->
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## What should we test?
|
#### What should we test?
|
||||||
<!-- List which features should be tested and how.
|
<!-- List which features should be tested and how.
|
||||||
This can be similar to the Steps to Reproduce in the issue.
|
This can be similar to the Steps to Reproduce in the issue.
|
||||||
Also think of other parts of the app which could be affected
|
Also think of other parts of the app which could be affected
|
||||||
@@ -16,16 +16,11 @@
|
|||||||
- Visit ... page.
|
- Visit ... page.
|
||||||
-
|
-
|
||||||
|
|
||||||
## Release notes
|
#### Release notes
|
||||||
|
|
||||||
<!-- Please select one for your PR and delete the other. -->
|
<!-- Please select one for your PR and delete the other. -->
|
||||||
|
|
||||||
Changelog Category (reviewers may add a label for the release notes):
|
Changelog Category: User facing changes | Technical changes
|
||||||
|
|
||||||
- [ ] User facing changes
|
|
||||||
- [ ] API changes (V0, V1, DFC or Webhook)
|
|
||||||
- [ ] Technical changes only
|
|
||||||
- [ ] Feature toggled
|
|
||||||
|
|
||||||
<!-- Choose a pull request title above which explains your change to a
|
<!-- Choose a pull request title above which explains your change to a
|
||||||
a user of the Open Food Network app. -->
|
a user of the Open Food Network app. -->
|
||||||
@@ -33,12 +28,12 @@ Changelog Category (reviewers may add a label for the release notes):
|
|||||||
The title of the pull request will be included in the release notes.
|
The title of the pull request will be included in the release notes.
|
||||||
|
|
||||||
|
|
||||||
## Dependencies
|
#### Dependencies
|
||||||
<!-- Does this PR depend on another one?
|
<!-- Does this PR depend on another one?
|
||||||
Add the link or remove this section. -->
|
Add the link or remove this section. -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Documentation updates
|
#### Documentation updates
|
||||||
<!-- Are there any wiki pages that need updating after merging this PR?
|
<!-- Are there any wiki pages that need updating after merging this PR?
|
||||||
List them here or remove this section. -->
|
List them here or remove this section. -->
|
||||||
|
|||||||
37
.github/dependabot.yml
vendored
37
.github/dependabot.yml
vendored
@@ -1,39 +1,11 @@
|
|||||||
# Dependabot configuration
|
|
||||||
#
|
|
||||||
# The `directory` and `schedule.interval` options are mandatory.
|
|
||||||
# Most of the configuration here is not used for security updates though.
|
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
multi-ecosystem-groups:
|
|
||||||
turbo_power:
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "bundler"
|
|
||||||
directory: "/"
|
|
||||||
patterns: ["turbo_power"]
|
|
||||||
multi-ecosystem-group: "turbo_power"
|
|
||||||
|
|
||||||
# Only specific requirements are specified in Gemfile, so don't touch it.
|
|
||||||
versioning-strategy: lockfile-only
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
patterns: ["turbo_power"]
|
|
||||||
multi-ecosystem-group: "turbo_power"
|
|
||||||
|
|
||||||
# Only specific requirements are specified in package.json, so don't touch it.
|
|
||||||
versioning-strategy: lockfile-only
|
|
||||||
|
|
||||||
- package-ecosystem: "bundler"
|
- package-ecosystem: "bundler"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
cooldown:
|
open-pull-requests-limit: 10
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Only specific requirements are specified in Gemfile, so don't touch it.
|
# Only specific requirements are specified in Gemfile, so don't touch it.
|
||||||
versioning-strategy: lockfile-only
|
versioning-strategy: lockfile-only
|
||||||
|
|
||||||
@@ -41,8 +13,5 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
cooldown:
|
# All versions are specified in package.json, so please update them.
|
||||||
default-days: 7
|
versioning-strategy: increase
|
||||||
|
|
||||||
# Only specific requirements are specified in package.json, so don't touch it.
|
|
||||||
versioning-strategy: lockfile-only
|
|
||||||
|
|||||||
43
.github/release.yml
vendored
43
.github/release.yml
vendored
@@ -1,43 +0,0 @@
|
|||||||
changelog:
|
|
||||||
# Categorise according to what an instance manager needs to know
|
|
||||||
categories:
|
|
||||||
# Add the right label if anything appears in here.
|
|
||||||
# Then re-generate the release notes.
|
|
||||||
- title: "❓❓❓ Uncategorised ❓❓❓"
|
|
||||||
labels:
|
|
||||||
- '*'
|
|
||||||
exclude:
|
|
||||||
labels:
|
|
||||||
- api changes
|
|
||||||
- dependencies
|
|
||||||
- feature toggled
|
|
||||||
- technical changes only
|
|
||||||
- user facing changes
|
|
||||||
|
|
||||||
# These will require a minor or major version increment
|
|
||||||
- title: "Significant changes 🚀"
|
|
||||||
labels:
|
|
||||||
- breaking change
|
|
||||||
- major breaking change
|
|
||||||
|
|
||||||
# Posted in advance for #instance-managers
|
|
||||||
- title: "User-facing changes 👀"
|
|
||||||
labels:
|
|
||||||
- user facing changes
|
|
||||||
|
|
||||||
- title: "API changes ⚠️"
|
|
||||||
labels:
|
|
||||||
- api changes
|
|
||||||
|
|
||||||
- title: "Experimental features for testing 🚧" # may be tested by instance managers
|
|
||||||
labels:
|
|
||||||
- feature toggled
|
|
||||||
|
|
||||||
# Instance managers ignore below
|
|
||||||
- title: "Technical changes 🛠️"
|
|
||||||
labels:
|
|
||||||
- technical changes only
|
|
||||||
|
|
||||||
- title: "Dependencies 📦"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
15
.github/test-events/dependabot-pr.json
vendored
15
.github/test-events/dependabot-pr.json
vendored
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"pull_request": {
|
|
||||||
"number": 13545,
|
|
||||||
"title": "Bump test from 7.0.4 to 7.0.8",
|
|
||||||
"user": {
|
|
||||||
"login": "dependabot[bot]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"owner": {
|
|
||||||
"login": "openfoodfoundation"
|
|
||||||
},
|
|
||||||
"name": "openfoodnetwork"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
.github/workflows/auto-author-assign.yml
vendored
14
.github/workflows/auto-author-assign.yml
vendored
@@ -1,14 +0,0 @@
|
|||||||
name: Auto Author Assign
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, reopened]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
assign-author:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: toshimaru/auto-author-assign@v2.1.0
|
|
||||||
51
.github/workflows/brakeman-analysis.yml
vendored
Normal file
51
.github/workflows/brakeman-analysis.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# This workflow integrates Brakeman with GitHub's Code Scanning feature
|
||||||
|
# Brakeman is a static analysis security vulnerability scanner for Ruby on Rails applications
|
||||||
|
|
||||||
|
name: Brakeman Scan
|
||||||
|
|
||||||
|
# This section configures the trigger for the workflow. Feel free to customize depending on your convention
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
brakeman-scan:
|
||||||
|
permissions:
|
||||||
|
contents: read # for actions/checkout to fetch code
|
||||||
|
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||||
|
name: Brakeman Scan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Checkout the repository to the GitHub Actions runner
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Customize the ruby version depending on your needs
|
||||||
|
- name: Setup Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: '2.7'
|
||||||
|
|
||||||
|
- name: Setup Brakeman
|
||||||
|
env:
|
||||||
|
BRAKEMAN_VERSION: '5.4.0'
|
||||||
|
run: |
|
||||||
|
gem install brakeman --version $BRAKEMAN_VERSION
|
||||||
|
|
||||||
|
# Execute Brakeman CLI and generate a SARIF output with the security issues identified during the analysis
|
||||||
|
- name: Scan
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
|
||||||
|
brakeman -f sarif -o output.sarif.json .
|
||||||
|
|
||||||
|
# Upload the SARIF file generated in the previous step
|
||||||
|
- name: Upload SARIF
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: output.sarif.json
|
||||||
307
.github/workflows/build.yml
vendored
307
.github/workflows/build.yml
vendored
@@ -17,7 +17,7 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
controllers_and_models:
|
knapsack_rspec_controllers:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -38,15 +38,15 @@ jobs:
|
|||||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||||
ci_node_total: [4]
|
ci_node_total: [8]
|
||||||
# Indexes for parallel jobs (starting from zero).
|
# Indexes for parallel jobs (starting from zero).
|
||||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||||
ci_node_index: [0, 1, 2, 3]
|
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup redis
|
- name: Setup redis
|
||||||
uses: supercharge/redis-github-action@1.8.1
|
uses: supercharge/redis-github-action@1.4.0
|
||||||
with:
|
with:
|
||||||
redis-version: 6
|
redis-version: 6
|
||||||
|
|
||||||
@@ -55,18 +55,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||||
|
|
||||||
# JS is required in order for webpacker to compile, in order to render templates containing image urls
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install JS dependencies
|
- name: Install JS dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set up database
|
- name: Set up database
|
||||||
run: |
|
run: |
|
||||||
bin/rails db:create db:schema:load
|
bundle exec rake db:create
|
||||||
|
bundle exec rake db:schema:load
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
env:
|
||||||
@@ -81,21 +80,12 @@ jobs:
|
|||||||
# RSpec split test files by test examples feature - it's optional
|
# RSpec split test files by test examples feature - it's optional
|
||||||
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||||
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb,spec/models/**/*_spec.rb}"
|
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}"
|
||||||
run: |
|
run: |
|
||||||
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
|
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
|
||||||
bin/rails assets:precompile knapsack_pro:rspec
|
bundle exec rake knapsack_pro:rspec
|
||||||
|
|
||||||
- name: Save SimpleCov file
|
knapsack_rspec_models:
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: simplecov-chunk-controllers-${{ matrix.ci_node_index }}
|
|
||||||
path: coverage/*.*
|
|
||||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
|
||||||
if-no-files-found: ignore
|
|
||||||
include-hidden-files: true
|
|
||||||
|
|
||||||
system:
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -116,15 +106,15 @@ jobs:
|
|||||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||||
ci_node_total: [19]
|
ci_node_total: [7]
|
||||||
# Indexes for parallel jobs (starting from zero).
|
# Indexes for parallel jobs (starting from zero).
|
||||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||||
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
|
ci_node_index: [0, 1, 2, 3, 4, 5, 6]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup redis
|
- name: Setup redis
|
||||||
uses: supercharge/redis-github-action@1.8.1
|
uses: supercharge/redis-github-action@1.4.0
|
||||||
with:
|
with:
|
||||||
redis-version: 6
|
redis-version: 6
|
||||||
|
|
||||||
@@ -136,14 +126,83 @@ jobs:
|
|||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install JS dependencies
|
- name: Install JS dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set up database
|
- name: Set up database
|
||||||
run: |
|
run: |
|
||||||
bin/rails db:create db:schema:load
|
bundle exec rake db:create
|
||||||
|
bundle exec rake db:schema:load
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
|
||||||
|
env:
|
||||||
|
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 09476e2ce491c12083df62768667c674
|
||||||
|
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
|
||||||
|
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
|
||||||
|
KNAPSACK_PRO_LOG_LEVEL: info
|
||||||
|
# if you use Knapsack Pro Queue Mode you must set below env variable
|
||||||
|
# to be able to retry CI build and run previously recorded tests
|
||||||
|
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
|
||||||
|
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
|
||||||
|
# RSpec split test files by test examples feature - it's optional
|
||||||
|
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||||
|
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||||
|
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
|
||||||
|
|
||||||
|
run: |
|
||||||
|
bundle exec rake knapsack_pro:rspec
|
||||||
|
|
||||||
|
knapsack_rspec_system_admin:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:10
|
||||||
|
ports: ["5432:5432"]
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
env:
|
||||||
|
POSTGRES_DB: open_food_network_test
|
||||||
|
POSTGRES_USER: ofn
|
||||||
|
POSTGRES_PASSWORD: f00d
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||||
|
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||||
|
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||||
|
ci_node_total: [10]
|
||||||
|
# Indexes for parallel jobs (starting from zero).
|
||||||
|
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||||
|
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup redis
|
||||||
|
uses: supercharge/redis-github-action@1.4.0
|
||||||
|
with:
|
||||||
|
redis-version: 6
|
||||||
|
|
||||||
|
- name: Set up Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version-file: .node-version
|
||||||
|
|
||||||
|
- name: Install JS dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Set up database
|
||||||
|
run: |
|
||||||
|
bundle exec rake db:create
|
||||||
|
bundle exec rake db:schema:load
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
||||||
@@ -159,30 +218,21 @@ jobs:
|
|||||||
# RSpec split test files by test examples feature - it's optional
|
# RSpec split test files by test examples feature - it's optional
|
||||||
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||||
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb,spec/system/consumer/**/*_spec.rb}"
|
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
|
||||||
|
|
||||||
run: |
|
run: |
|
||||||
bin/rails assets:precompile knapsack_pro:queue:rspec
|
bundle exec rake knapsack_pro:queue:rspec
|
||||||
|
|
||||||
- name: Save SimpleCov file
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: simplecov-chunk-system-${{ matrix.ci_node_index }}
|
|
||||||
path: coverage/*.*
|
|
||||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
|
||||||
if-no-files-found: ignore
|
|
||||||
include-hidden-files: true
|
|
||||||
|
|
||||||
- name: Archive failed tests screenshots
|
- name: Archive failed tests screenshots
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: failed-system_${{ matrix.ci_node_index }}-tests-screenshots
|
name: failed-tests-screenshots
|
||||||
path: tmp/capybara/screenshots/*.png
|
path: tmp/capybara/screenshots/*.png
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
|
|
||||||
engines:
|
knapsack_rspec_system_consumer:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -203,15 +253,15 @@ jobs:
|
|||||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||||
ci_node_total: [2]
|
ci_node_total: [10]
|
||||||
# Indexes for parallel jobs (starting from zero).
|
# Indexes for parallel jobs (starting from zero).
|
||||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||||
ci_node_index: [0, 1]
|
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup redis
|
- name: Setup redis
|
||||||
uses: supercharge/redis-github-action@1.8.1
|
uses: supercharge/redis-github-action@1.4.0
|
||||||
with:
|
with:
|
||||||
redis-version: 6
|
redis-version: 6
|
||||||
|
|
||||||
@@ -220,18 +270,95 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||||
|
|
||||||
# JS is required in order for webpacker to compile, in order to render templates linking to mail.css
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install JS dependencies
|
- name: Install JS dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set up database
|
- name: Set up database
|
||||||
run: |
|
run: |
|
||||||
bin/rails db:create db:schema:load
|
bundle exec rake db:create
|
||||||
|
bundle exec rake db:schema:load
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
|
||||||
|
env:
|
||||||
|
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: e52bd4390c853e6c5bdfe4d0334586c1
|
||||||
|
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
|
||||||
|
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
|
||||||
|
KNAPSACK_PRO_LOG_LEVEL: info
|
||||||
|
# if you use Knapsack Pro Queue Mode you must set below env variable
|
||||||
|
# to be able to retry CI build and run previously recorded tests
|
||||||
|
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
|
||||||
|
KNAPSACK_PRO_FIXED_QUEUE_SPLIT: true
|
||||||
|
# RSpec split test files by test examples feature - it's optional
|
||||||
|
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||||
|
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||||
|
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
|
||||||
|
|
||||||
|
run: |
|
||||||
|
bundle exec rake knapsack_pro:queue:rspec
|
||||||
|
|
||||||
|
- name: Archive failed tests screenshots
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: failed-tests-screenshots
|
||||||
|
path: tmp/capybara/screenshots/*.png
|
||||||
|
retention-days: 7
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
knapsack_rspec_engines:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:10
|
||||||
|
ports: ["5432:5432"]
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
env:
|
||||||
|
POSTGRES_DB: open_food_network_test
|
||||||
|
POSTGRES_USER: ofn
|
||||||
|
POSTGRES_PASSWORD: f00d
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||||
|
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||||
|
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||||
|
ci_node_total: [5]
|
||||||
|
# Indexes for parallel jobs (starting from zero).
|
||||||
|
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||||
|
ci_node_index: [0, 1, 2, 3, 4]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup redis
|
||||||
|
uses: supercharge/redis-github-action@1.4.0
|
||||||
|
with:
|
||||||
|
redis-version: 6
|
||||||
|
|
||||||
|
- name: Set up Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version-file: .node-version
|
||||||
|
|
||||||
|
- name: Install JS dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Set up database
|
||||||
|
run: |
|
||||||
|
bundle exec rake db:create
|
||||||
|
bundle exec rake db:schema:load
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
||||||
@@ -250,18 +377,18 @@ jobs:
|
|||||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
|
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
|
||||||
|
|
||||||
run: |
|
run: |
|
||||||
bin/rails assets:precompile knapsack_pro:rspec
|
bundle exec rake knapsack_pro:rspec
|
||||||
|
|
||||||
- name: Save SimpleCov file
|
- name: Archive failed tests screenshots
|
||||||
uses: actions/upload-artifact@v4
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: simplecov-chunk-engines-${{ matrix.ci_node_index }}
|
name: failed-tests-screenshots
|
||||||
path: coverage/*.*
|
path: tmp/capybara/screenshots/*.png
|
||||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
retention-days: 7
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
include-hidden-files: true
|
|
||||||
|
|
||||||
test_the_rest:
|
knapsack_rspec_test_the_rest:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -282,15 +409,15 @@ jobs:
|
|||||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||||
ci_node_total: [3]
|
ci_node_total: [5]
|
||||||
# Indexes for parallel jobs (starting from zero).
|
# Indexes for parallel jobs (starting from zero).
|
||||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||||
ci_node_index: [0, 1, 2]
|
ci_node_index: [0, 1, 2, 3, 4]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup redis
|
- name: Setup redis
|
||||||
uses: supercharge/redis-github-action@1.8.1
|
uses: supercharge/redis-github-action@1.4.0
|
||||||
with:
|
with:
|
||||||
redis-version: 6
|
redis-version: 6
|
||||||
|
|
||||||
@@ -299,20 +426,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||||
|
|
||||||
# JS is required in order for webpacker to compile, in order to render templates linking to mail.css
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install JS dependencies
|
- name: Install JS dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set up database
|
- name: Set up database
|
||||||
run: |
|
run: |
|
||||||
bin/rails db:create db:schema:load
|
bundle exec rake db:create
|
||||||
|
bundle exec rake db:schema:load
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
||||||
env:
|
env:
|
||||||
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: e3b8800198d2d89b70c7edbdd85f8fd8
|
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: e3b8800198d2d89b70c7edbdd85f8fd8
|
||||||
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
|
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
|
||||||
@@ -326,17 +453,10 @@ jobs:
|
|||||||
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||||
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||||
KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}"
|
KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}"
|
||||||
run: |
|
|
||||||
bin/rails assets:precompile knapsack_pro:rspec
|
|
||||||
|
|
||||||
- name: Save SimpleCov file
|
|
||||||
uses: actions/upload-artifact@v4
|
run: |
|
||||||
with:
|
bundle exec rake knapsack_pro:rspec
|
||||||
name: simplecov-chunk-the-rest-${{ matrix.ci_node_index }}
|
|
||||||
path: coverage/*.*
|
|
||||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
|
||||||
if-no-files-found: ignore
|
|
||||||
include-hidden-files: true
|
|
||||||
|
|
||||||
non_knapsack_jest_karma:
|
non_knapsack_jest_karma:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
@@ -356,7 +476,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
# Rails is required for the Karma rake script
|
- name: Setup redis
|
||||||
|
uses: supercharge/redis-github-action@1.4.0
|
||||||
|
with:
|
||||||
|
redis-version: 6
|
||||||
|
|
||||||
- name: Set up Ruby
|
- name: Set up Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
@@ -365,51 +489,16 @@ jobs:
|
|||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install JS dependencies
|
- name: Install JS dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Set up database
|
||||||
|
run: |
|
||||||
|
bundle exec rake db:create
|
||||||
|
bundle exec rake db:schema:load
|
||||||
- name: Run JS tests
|
- name: Run JS tests
|
||||||
run: bin/rake karma:run
|
run: bundle exec rake karma:run
|
||||||
|
|
||||||
- name: Run jest tests
|
- name: Run jest tests
|
||||||
run: yarn jest
|
run: yarn jest
|
||||||
|
|
||||||
collate_simplecov_results:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
needs:
|
|
||||||
- controllers_and_models
|
|
||||||
- engines
|
|
||||||
- system
|
|
||||||
- test_the_rest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Ruby
|
|
||||||
uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
||||||
|
|
||||||
- name: Download individual results from individual runners
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
pattern: simplecov-chunk-*
|
|
||||||
path: tmp/simplecov
|
|
||||||
|
|
||||||
- name: collate results from each of the workers
|
|
||||||
run: bundle exec rake 'simplecov:collate_results[tmp/simplecov]'
|
|
||||||
|
|
||||||
- name: Upload collated results
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: combined-simplecov-report
|
|
||||||
path: coverage/**/*.*
|
|
||||||
retention-days: 7
|
|
||||||
if-no-files-found: ignore
|
|
||||||
include-hidden-files: true
|
|
||||||
- name: Compare SimpleCov results with Undercover
|
|
||||||
run: |
|
|
||||||
git fetch --no-tags origin ${{ github.event.pull_request.base.ref }}:master
|
|
||||||
bundle exec undercover
|
|
||||||
if: ${{ github.ref != 'refs/heads/master' }} # Does not run on master, as we can't fetch master in the master branch
|
|
||||||
|
|||||||
45
.github/workflows/linters.yml
vendored
45
.github/workflows/linters.yml
vendored
@@ -1,30 +1,47 @@
|
|||||||
name: Linters
|
name: Linters
|
||||||
on: [pull_request]
|
on: [push, pull_request]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
rubocop:
|
||||||
name: reviewdog
|
name: runner / rubocop
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- uses: ruby/setup-ruby@v1
|
||||||
|
|
||||||
|
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
|
||||||
|
|
||||||
|
- name: rubocop
|
||||||
|
uses: reviewdog/action-rubocop@v2
|
||||||
|
with:
|
||||||
|
rubocop_version: gemfile
|
||||||
|
rubocop_extensions: rubocop-rails:gemfile
|
||||||
|
reporter: github-pr-check
|
||||||
|
level: error
|
||||||
|
fail_on_error: true
|
||||||
|
prettier:
|
||||||
|
name: runner / prettier
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
|
|
||||||
- name: Install JS dependencies
|
- name: Install JS dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
||||||
|
|
||||||
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
|
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
|
||||||
|
|
||||||
- uses: reviewdog/action-setup@v1
|
- name: prettier
|
||||||
|
uses: EPMatt/reviewdog-action-prettier@v1
|
||||||
with:
|
with:
|
||||||
reviewdog_version: v0.21.0
|
github_token: ${{ secrets.github_token }}
|
||||||
|
reporter: github-pr-check
|
||||||
- run: ./script/reviewdog.sh
|
level: error
|
||||||
env:
|
fail_on_error: true
|
||||||
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.github_token }}
|
|
||||||
|
|||||||
51
.github/workflows/mapi.yml
vendored
Normal file
51
.github/workflows/mapi.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
name: 'Mayhem for API'
|
||||||
|
on: workflow_dispatch
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
security-events: write # to upload SARIF results (github/codeql-action/upload-sarif)
|
||||||
|
if: ${{ github.repository_owner == 'openfoodfoundation' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- run: docker/build
|
||||||
|
- run: docker-compose up --detach
|
||||||
|
- run: until curl -f -s http://localhost:3000; do echo "waiting for api server"; sleep 1; done
|
||||||
|
- run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="update spree_users set spree_api_key='testing' where login='ofn@example.com'"
|
||||||
|
# equivalent to Flipper.enable(:api_v1)
|
||||||
|
- run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="insert into flipper_features (key, created_at, updated_at) values ('api_v1', localtimestamp, localtimestamp)"
|
||||||
|
- run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="insert into flipper_gates (feature_key, key, value, created_at, updated_at) values ('api_v1', 'boolean', 'true', localtimestamp, localtimestamp)"
|
||||||
|
|
||||||
|
# Run Mayhem for API
|
||||||
|
- name: Run Mayhem for API
|
||||||
|
uses: ForAllSecure/mapi-action@v1
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
mapi-token: ${{ secrets.MAPI_TOKEN }}
|
||||||
|
api-url: http://localhost:3000
|
||||||
|
api-spec: swagger/v1/swagger.yaml
|
||||||
|
target: openfoodfoundation/openfoodnetwork
|
||||||
|
duration: 1min
|
||||||
|
sarif-report: mapi.sarif
|
||||||
|
html-report: mapi.html
|
||||||
|
run-args: |
|
||||||
|
--header-auth
|
||||||
|
X-Api-Token: testing
|
||||||
|
|
||||||
|
# Archive HTML report
|
||||||
|
- name: Archive Mayhem for API report
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: mapi-report
|
||||||
|
path: mapi.html
|
||||||
|
|
||||||
|
# Upload SARIF file (only available on public repos or github enterprise)
|
||||||
|
- name: Upload SARIF file
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: mapi.sarif
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
name: Auto-move Dependabot PRs to Code Review
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
move-pr-to-code-review:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event.pull_request.user.login == 'dependabot[bot]' || startsWith(github.event.pull_request.title, 'Bump')
|
|
||||||
steps:
|
|
||||||
- name: Generate GitHub App Token
|
|
||||||
id: app-token
|
|
||||||
uses: tibdex/github-app-token@v2
|
|
||||||
with:
|
|
||||||
app_id: ${{ secrets.DEPENDABOT_PR_APP_ID }}
|
|
||||||
private_key: ${{ secrets.DEPENDABOT_PR_APP_PRIVATE_KEY }}
|
|
||||||
installation_retrieval_mode: id
|
|
||||||
installation_retrieval_payload: ${{ secrets.DEPENDABOT_PR_APP_INSTALLATION_ID }}
|
|
||||||
|
|
||||||
- name: Move PR to Code Review in Project v2
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
github-token: ${{ steps.app-token.outputs.token }}
|
|
||||||
script: |
|
|
||||||
const projectNumber = 8; // for "OFN Delivery board"
|
|
||||||
const org = "openfoodfoundation";
|
|
||||||
const repo = context.repo.repo;
|
|
||||||
const prNumber = context.payload.pull_request.number;
|
|
||||||
const statusFieldName = "Status";
|
|
||||||
const statusValue = "Code review 🔎";
|
|
||||||
|
|
||||||
// ---- Helper: Get PR Node ID ----
|
|
||||||
async function getPrNodeId(owner, repo, number) {
|
|
||||||
const res = await github.graphql(`
|
|
||||||
query($owner: String!, $repo: String!, $number: Int!) {
|
|
||||||
repository(owner: $owner, name: $repo) {
|
|
||||||
pullRequest(number: $number) {
|
|
||||||
id
|
|
||||||
number
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, { owner, repo, number });
|
|
||||||
return res.repository.pullRequest.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("🚀 Starting ProjectV2 automation...");
|
|
||||||
|
|
||||||
// ---- Step 1: Get Project and Fields ----
|
|
||||||
const projectRes = await github.graphql(`
|
|
||||||
query($org: String!, $number: Int!) {
|
|
||||||
organization(login: $org) {
|
|
||||||
projectV2(number: $number) {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
fields(first: 50) {
|
|
||||||
nodes {
|
|
||||||
... on ProjectV2Field {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
... on ProjectV2SingleSelectField {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
options {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, { org, number: projectNumber });
|
|
||||||
|
|
||||||
const project = projectRes.organization.projectV2;
|
|
||||||
if (!project) throw new Error(`❌ Project #${projectNumber} not found`);
|
|
||||||
|
|
||||||
console.log(`✅ Found project: ${project.title} (${project.id})`);
|
|
||||||
|
|
||||||
const statusField = project.fields.nodes.find(f => f.name === statusFieldName);
|
|
||||||
if (!statusField) throw new Error(`❌ Field '${statusFieldName}' not found`);
|
|
||||||
|
|
||||||
const option = statusField.options.find(o => o.name === statusValue);
|
|
||||||
if (!option) throw new Error(`❌ Option '${statusValue}' not found in '${statusFieldName}'`);
|
|
||||||
|
|
||||||
console.log(`✅ Found field '${statusFieldName}' and option '${statusValue}'`);
|
|
||||||
|
|
||||||
// ---- Step 2: Get PR Node ID ----
|
|
||||||
const prNodeId = await getPrNodeId(org, repo, prNumber);
|
|
||||||
console.log(`✅ PR #${prNumber} node ID: ${prNodeId}`);
|
|
||||||
|
|
||||||
// ---- Step 3: Check if PR is already in Project ----
|
|
||||||
const itemRes = await github.graphql(`
|
|
||||||
query($prId: ID!) {
|
|
||||||
node(id: $prId) {
|
|
||||||
... on PullRequest {
|
|
||||||
projectItems(first: 50) {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
project { id title }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, { prId: prNodeId });
|
|
||||||
|
|
||||||
let projectItem = itemRes.node.projectItems.nodes.find(i => i.project.id === project.id);
|
|
||||||
|
|
||||||
if (!projectItem) {
|
|
||||||
console.log("ℹ️ PR not yet in project, adding...");
|
|
||||||
const addRes = await github.graphql(`
|
|
||||||
mutation($projectId: ID!, $contentId: ID!) {
|
|
||||||
addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) {
|
|
||||||
item { id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, { projectId: project.id, contentId: prNodeId });
|
|
||||||
projectItem = addRes.addProjectV2ItemById.item;
|
|
||||||
console.log(`✅ Added PR to project: ${projectItem.id}`);
|
|
||||||
} else {
|
|
||||||
console.log(`ℹ️ PR already in project: ${projectItem.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Step 4: Update Status ----
|
|
||||||
await github.graphql(`
|
|
||||||
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
||||||
updateProjectV2ItemFieldValue(input: {
|
|
||||||
projectId: $projectId,
|
|
||||||
itemId: $itemId,
|
|
||||||
fieldId: $fieldId,
|
|
||||||
value: { singleSelectOptionId: $optionId }
|
|
||||||
}) {
|
|
||||||
projectV2Item { id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, {
|
|
||||||
projectId: project.id,
|
|
||||||
itemId: projectItem.id,
|
|
||||||
fieldId: statusField.id,
|
|
||||||
optionId: option.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`🎉 Moved PR #${prNumber} → '${statusValue}'`);
|
|
||||||
6
.github/workflows/stage.yml
vendored
6
.github/workflows/stage.yml
vendored
@@ -13,10 +13,6 @@ on:
|
|||||||
- staging.openfoodnetwork.org.uk
|
- staging.openfoodnetwork.org.uk
|
||||||
- staging.openfoodnetwork.org.au
|
- staging.openfoodnetwork.org.au
|
||||||
- staging.coopcircuits.fr
|
- staging.coopcircuits.fr
|
||||||
commit_ref:
|
|
||||||
description: "Commit Reference"
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy_pr:
|
deploy_pr:
|
||||||
@@ -63,4 +59,4 @@ jobs:
|
|||||||
- name: Deploy to Staging
|
- name: Deploy to Staging
|
||||||
if: success()
|
if: success()
|
||||||
run: |
|
run: |
|
||||||
ssh ofn-deploy@${{ inputs.server }} -o LogLevel=ERROR "$GITHUB_REF_NAME ${{ inputs.commit_ref || github.sha }}"
|
ssh ofn-deploy@${{ inputs.server }} -o LogLevel=ERROR "$GITHUB_REF_NAME $GITHUB_SHA"
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -59,4 +59,3 @@ yarn-debug.log*
|
|||||||
|
|
||||||
/config/credentials.yml.enc
|
/config/credentials.yml.enc
|
||||||
/config/master.key
|
/config/master.key
|
||||||
.secrets
|
|
||||||
|
|||||||
@@ -2,12 +2,19 @@
|
|||||||
# frameworks such as Jekyll/Middleman
|
# frameworks such as Jekyll/Middleman
|
||||||
skip_frontmatter: false
|
skip_frontmatter: false
|
||||||
|
|
||||||
inherits_from: .haml-lint_todo.yml
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
AltText:
|
AltText:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
ClassAttributeWithStaticValue:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ClassesBeforeIds:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ConsecutiveComments:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
ConsecutiveSilentScripts:
|
ConsecutiveSilentScripts:
|
||||||
enabled: true
|
enabled: true
|
||||||
max_consecutive: 2
|
max_consecutive: 2
|
||||||
@@ -25,6 +32,7 @@ linters:
|
|||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
LineLength:
|
LineLength:
|
||||||
|
enabled: true
|
||||||
max: 80
|
max: 80
|
||||||
|
|
||||||
MultilinePipe:
|
MultilinePipe:
|
||||||
@@ -39,11 +47,24 @@ linters:
|
|||||||
RuboCop:
|
RuboCop:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
RubyComments:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
SpaceBeforeScript:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
SpaceInsideHashAttributes:
|
SpaceInsideHashAttributes:
|
||||||
|
enabled: true
|
||||||
style: no_space
|
style: no_space
|
||||||
|
|
||||||
TagName:
|
TagName:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
TrailingWhitespace:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
UnnecessaryInterpolation:
|
UnnecessaryInterpolation:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
UnnecessaryStringOutput:
|
||||||
|
enabled: true
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
# This configuration was generated by
|
|
||||||
# `haml-lint --auto-gen-config`
|
|
||||||
# on 2025-10-30 09:19:50 +0100 using Haml-Lint version 0.66.0.
|
|
||||||
# The point is for the user to remove these configuration records
|
|
||||||
# one by one as the lints are removed from the code base.
|
|
||||||
# Note that changes in the inspected code, or installation of new
|
|
||||||
# versions of Haml-Lint, may require this file to be generated again.
|
|
||||||
|
|
||||||
linters:
|
|
||||||
|
|
||||||
# Offense count: 35
|
|
||||||
ClassAttributeWithStaticValue:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 77
|
|
||||||
ClassesBeforeIds:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 18
|
|
||||||
ConsecutiveComments:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 22
|
|
||||||
ConsecutiveSilentScripts:
|
|
||||||
exclude:
|
|
||||||
- "app/views/admin/contents/_fieldset.html.haml"
|
|
||||||
- "app/views/admin/enterprises/form/_tag_rules.html.haml"
|
|
||||||
- "app/views/admin/order_cycles/edit.html.haml"
|
|
||||||
- "app/views/admin/products_v3/product_preview.turbo_stream.haml"
|
|
||||||
- "app/views/admin/reports/_date_range_form.html.haml"
|
|
||||||
- "app/views/checkout/_details.html.haml"
|
|
||||||
- "app/views/checkout/_payment.html.haml"
|
|
||||||
- "app/views/spree/admin/adjustments/_adjustments_table.html.haml"
|
|
||||||
- "app/views/spree/admin/orders/customer_details/_address_form.html.haml"
|
|
||||||
- "app/views/spree/admin/tax_categories/index.html.haml"
|
|
||||||
- "app/views/spree/admin/users/index.html.haml"
|
|
||||||
|
|
||||||
# Offense count: 14
|
|
||||||
FinalNewline:
|
|
||||||
exclude:
|
|
||||||
- "app/assets/javascripts/templates/shared/question_mark_with_tooltip.html.haml"
|
|
||||||
- "app/views/admin/enterprises/form/_social.html.haml"
|
|
||||||
- "app/views/admin/json/_injection_ams.html.haml"
|
|
||||||
- "app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml"
|
|
||||||
- "app/views/admin/order_cycles/edit.html.haml"
|
|
||||||
- "app/views/admin/product_import/_ams_data.html.haml"
|
|
||||||
- "app/views/admin/reports/_row_group.haml"
|
|
||||||
- "app/views/admin/reports/filters/_enterprise_fee_summary.html.haml"
|
|
||||||
- "app/views/admin/reports/filters/_users_and_enterprises.html.haml"
|
|
||||||
- "app/views/shop/_blocked_cookies.html.haml"
|
|
||||||
- "app/views/spree/admin/orders/_invoice/_order_note.html.haml"
|
|
||||||
- "app/views/spree/admin/orders/invoice4.html.haml"
|
|
||||||
- "app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml"
|
|
||||||
- "app/views/spree/admin/users/_email_confirmation.html.haml"
|
|
||||||
|
|
||||||
# Offense count: 130
|
|
||||||
IdNames:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 5
|
|
||||||
Indentation:
|
|
||||||
exclude:
|
|
||||||
- "app/views/admin/products_v3/clone.turbo_stream.haml"
|
|
||||||
- "app/views/admin/products_v3/destroy_product_variant.turbo_stream.haml"
|
|
||||||
- "app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml"
|
|
||||||
|
|
||||||
# Offense count: 191
|
|
||||||
InlineStyles:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 589
|
|
||||||
InstanceVariables:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
LeadingCommentSpace:
|
|
||||||
exclude:
|
|
||||||
- "app/views/admin/reports/_row_group.haml"
|
|
||||||
|
|
||||||
# Offense count: 2331
|
|
||||||
LineLength:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
MultilinePipe:
|
|
||||||
exclude:
|
|
||||||
- "app/views/admin/reports/_rendering_options.html.haml"
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
MultilineScript:
|
|
||||||
exclude:
|
|
||||||
- "app/views/admin/products_v3/product_preview.turbo_stream.haml"
|
|
||||||
- "app/views/checkout/_voucher_section.html.haml"
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
RepeatedId:
|
|
||||||
exclude:
|
|
||||||
- "app/assets/javascripts/templates/admin/save_bar.html.haml"
|
|
||||||
|
|
||||||
# Offense count: 24
|
|
||||||
RubyComments:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 104
|
|
||||||
SpaceBeforeScript:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 3345
|
|
||||||
SpaceInsideHashAttributes:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 22
|
|
||||||
TrailingEmptyLines:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 73
|
|
||||||
TrailingWhitespace:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 13
|
|
||||||
UnnecessaryInterpolation:
|
|
||||||
exclude:
|
|
||||||
- "app/components/example_component/example_component.html.haml"
|
|
||||||
- "app/views/admin/product_import/_entries_table.html.haml"
|
|
||||||
- "app/views/admin/product_import/import.html.haml"
|
|
||||||
- "app/views/admin/variant_overrides/_filters.html.haml"
|
|
||||||
- "app/views/registration/steps/_introduction.html.haml"
|
|
||||||
- "app/views/spree/order_mailer/_shipping.html.haml"
|
|
||||||
- "app/views/spree/order_mailer/invoice_email.html.haml"
|
|
||||||
- "app/views/spree/shared/_shipment_delivery_details.html.haml"
|
|
||||||
- "app/views/spree/shared/_shipment_pickup_details.html.haml"
|
|
||||||
|
|
||||||
# Offense count: 68
|
|
||||||
UnnecessaryStringOutput:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# Offense count: 14
|
|
||||||
ViewLength:
|
|
||||||
exclude:
|
|
||||||
- "app/assets/javascripts/templates/admin/panels/enterprise_package.html.haml"
|
|
||||||
- "app/views/admin/customers/index.html.haml"
|
|
||||||
- "app/views/admin/enterprises/_new_form.html.haml"
|
|
||||||
- "app/views/admin/enterprises/form/_shop_preferences.html.haml"
|
|
||||||
- "app/views/admin/product_import/_import_review.html.haml"
|
|
||||||
- "app/views/admin/products_v3/product_preview.turbo_stream.haml"
|
|
||||||
- "app/views/checkout/_details.html.haml"
|
|
||||||
- "app/views/groups/show.html.haml"
|
|
||||||
- "app/views/producer_mailer/order_cycle_report.html.haml"
|
|
||||||
- "app/views/shared/_footer.html.haml"
|
|
||||||
- "app/views/spree/admin/orders/bulk_management.html.haml"
|
|
||||||
- "app/views/spree/admin/orders/invoice4.html.haml"
|
|
||||||
- "app/views/spree/admin/products/new.html.haml"
|
|
||||||
- "app/views/spree/admin/variants/_form.html.haml"
|
|
||||||
6
.hound.yml
Normal file
6
.hound.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
rubocop:
|
||||||
|
config_file: .rubocop_styleguide.yml
|
||||||
|
scss:
|
||||||
|
config_file: .scss-lint.yml
|
||||||
|
haml:
|
||||||
|
config_file: .haml-lint.yml
|
||||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
yarn pretty-quick --check --staged
|
||||||
@@ -1 +1 @@
|
|||||||
24.10.0
|
17.9.1
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
*.yaml
|
*.yaml
|
||||||
*.json
|
*.json
|
||||||
*.html
|
*.html
|
||||||
**/*.rb
|
|
||||||
|
|
||||||
# JS
|
# JS
|
||||||
# Enabled: app/webpacker/controllers/*.js and app/webpacker/packs/*.js
|
# Enabled: app/webpacker/controllers/*.js and app/webpacker/packs/*.js
|
||||||
@@ -14,8 +13,7 @@ postcss.config.js
|
|||||||
|
|
||||||
# SCSS
|
# SCSS
|
||||||
# Enabled: most of admin
|
# Enabled: most of admin
|
||||||
/app/webpacker/css/admin/globals/mixins.scss
|
/app/webpacker/css/admin/globals/
|
||||||
/app/webpacker/css/admin/globals/variables.scss
|
|
||||||
/app/webpacker/css/admin/shared/
|
/app/webpacker/css/admin/shared/
|
||||||
/app/webpacker/css/admin_v3/globals/variables.scss
|
/app/webpacker/css/admin_v3/globals/variables.scss
|
||||||
/app/webpacker/css/darkswarm/
|
/app/webpacker/css/darkswarm/
|
||||||
@@ -28,5 +26,6 @@ postcss.config.js
|
|||||||
/coverage/
|
/coverage/
|
||||||
/engines/
|
/engines/
|
||||||
/public/
|
/public/
|
||||||
|
/spec/
|
||||||
/tmp/
|
/tmp/
|
||||||
/vendor/
|
/vendor/
|
||||||
|
|||||||
11
.rubocop.yml
11
.rubocop.yml
@@ -4,13 +4,7 @@
|
|||||||
#
|
#
|
||||||
# The configuration is split into three files. Look into those files for more details.
|
# The configuration is split into three files. Look into those files for more details.
|
||||||
#
|
#
|
||||||
plugins:
|
require: rubocop-rails
|
||||||
- rubocop-capybara
|
|
||||||
- rubocop-factory_bot
|
|
||||||
- rubocop-rails
|
|
||||||
- rubocop-rspec
|
|
||||||
- rubocop-rspec_rails
|
|
||||||
|
|
||||||
inherit_from:
|
inherit_from:
|
||||||
|
|
||||||
# The automatically generated todo list to ignore all current violations.
|
# The automatically generated todo list to ignore all current violations.
|
||||||
@@ -19,10 +13,9 @@ inherit_from:
|
|||||||
# The relaxed style rules as a common starting point which we can refine.
|
# The relaxed style rules as a common starting point which we can refine.
|
||||||
- .rubocop_relaxed_styleguide.yml
|
- .rubocop_relaxed_styleguide.yml
|
||||||
|
|
||||||
# Our Open Food Network style guides. If you want to see all violations,
|
# Our Open Food Network style guide. If you want to see all violations,
|
||||||
# then use only that configuration:
|
# then use only that configuration:
|
||||||
#
|
#
|
||||||
# bundle exec rubocop -c .rubocop_styleguide.yml
|
# bundle exec rubocop -c .rubocop_styleguide.yml
|
||||||
#
|
#
|
||||||
- .rubocop_styleguide.yml
|
- .rubocop_styleguide.yml
|
||||||
- .rubocop_rspec_styleguide.yml
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# OFN styleguide for rubocop-rspec
|
|
||||||
# Because there are so many, we will disable by default, and enable rules as needed.
|
|
||||||
|
|
||||||
Capybara:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
RSpec:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
FactoryBot:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Enabled rules
|
|
||||||
|
|
||||||
Capybara/NegationMatcher:
|
|
||||||
Enabled: true
|
|
||||||
EnforcedStyle: not_to
|
|
||||||
|
|
||||||
RSpec/ExpectChange:
|
|
||||||
Enabled: true
|
|
||||||
EnforcedStyle: block
|
|
||||||
|
|
||||||
RSpec/NotToNot:
|
|
||||||
Enabled: true
|
|
||||||
@@ -3,61 +3,24 @@
|
|||||||
# These are the rules we agreed upon and we work towards.
|
# These are the rules we agreed upon and we work towards.
|
||||||
AllCops:
|
AllCops:
|
||||||
NewCops: enable
|
NewCops: enable
|
||||||
MigratedSchemaVersion: "20250111000000"
|
SuggestExtensions: false
|
||||||
Exclude:
|
Exclude:
|
||||||
- bin/**/*
|
- 'bin/**/*'
|
||||||
- config/**/*
|
- 'db/**/*'
|
||||||
- db/bad_migrations/*
|
- 'config/**/*'
|
||||||
- db/migrate/201*
|
- 'script/**/*'
|
||||||
- db/migrate/202[0-4]*
|
- 'vendor/**/*'
|
||||||
- db/schema.rb
|
- 'node_modules/**/*'
|
||||||
- script/**/*
|
|
||||||
- vendor/**/*
|
|
||||||
- node_modules/**/*
|
|
||||||
# Excluding: inadequate Naming/FileName rule rejects GemFile name with camelcase
|
# Excluding: inadequate Naming/FileName rule rejects GemFile name with camelcase
|
||||||
- engines/web/Gemfile
|
- 'engines/web/Gemfile'
|
||||||
- .undercover
|
|
||||||
|
|
||||||
Bundler/DuplicatedGem:
|
## OFN SETTINGS
|
||||||
Enabled: false
|
#
|
||||||
|
# Cop settings that have been agreed upon by the OFN community
|
||||||
Layout/LineLength:
|
|
||||||
Enabled: true
|
|
||||||
Max: 100
|
|
||||||
|
|
||||||
Layout/MultilineMethodCallIndentation:
|
|
||||||
Enabled: true
|
|
||||||
EnforcedStyle: indented
|
|
||||||
|
|
||||||
# Don't think this is a big issue, mostly picking up RPSEC scope definitions
|
|
||||||
# with lamdas and RSpec '.to change{}' blocks
|
|
||||||
Lint/AmbiguousBlockAssociation:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
Lint/MissingSuper:
|
|
||||||
Exclude:
|
|
||||||
- app/components/**/*
|
|
||||||
|
|
||||||
Lint/RaiseException:
|
|
||||||
Enabled: true
|
|
||||||
|
|
||||||
Lint/StructNewOverride:
|
|
||||||
Enabled: true
|
|
||||||
|
|
||||||
# Heaps of offences (> 100) in specs, mostly in situations where two or more
|
|
||||||
# instances of a model are required, but only one is referenced. Difficult to
|
|
||||||
# fix without making the spec look messy or rewriting it.
|
|
||||||
# Should definitely fix at some point.
|
|
||||||
Lint/UselessAssignment:
|
|
||||||
Exclude:
|
|
||||||
- spec/**/*
|
|
||||||
|
|
||||||
Metrics:
|
Metrics:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
Metrics/AbcSize:
|
|
||||||
Max: 30 # default 17
|
|
||||||
|
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
AllowedMethods: [
|
AllowedMethods: [
|
||||||
"class_eval",
|
"class_eval",
|
||||||
@@ -76,84 +39,33 @@ Metrics/BlockLength:
|
|||||||
"put",
|
"put",
|
||||||
"resource",
|
"resource",
|
||||||
"resources",
|
"resources",
|
||||||
"response",
|
|
||||||
"scenario",
|
"scenario",
|
||||||
"shared_examples",
|
"shared_examples",
|
||||||
"shared_examples_for",
|
"shared_examples_for",
|
||||||
"xdescribe",
|
"xdescribe",
|
||||||
]
|
]
|
||||||
|
|
||||||
Metrics/MethodLength:
|
|
||||||
Enabled: true
|
|
||||||
Max: 25 # default 10
|
|
||||||
|
|
||||||
Metrics/ParameterLists:
|
Metrics/ParameterLists:
|
||||||
CountKeywordArgs: false
|
CountKeywordArgs: false
|
||||||
|
|
||||||
Metrics/PerceivedComplexity:
|
|
||||||
Enabled: true
|
|
||||||
Max: 14 # default 8
|
|
||||||
|
|
||||||
Naming/PredicatePrefix:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
Naming/VariableNumber:
|
|
||||||
AllowedIdentifiers:
|
|
||||||
- street_address_1
|
|
||||||
- street_address_2
|
|
||||||
AllowedPatterns:
|
|
||||||
- _v[\d]+
|
|
||||||
# Cf. conversation https://github.com/openfoodfoundation/openfoodnetwork/pull/13306#pullrequestreview-2831644286
|
|
||||||
- menu_[\d]
|
|
||||||
|
|
||||||
Rails/ApplicationRecord:
|
Rails/ApplicationRecord:
|
||||||
Exclude:
|
Exclude:
|
||||||
# Migrations should not contain application code:
|
# Migrations should not contain application code:
|
||||||
- db/migrate/*.rb
|
- "db/migrate/*.rb"
|
||||||
|
|
||||||
# Allow many-to-many associations without explicit model.
|
|
||||||
# - It avoids the additional code of a model class.
|
|
||||||
# - It simplifies the declaration of the association.
|
|
||||||
# - Rails may know that there are no callbacks associated.
|
|
||||||
Rails/HasAndBelongsToMany:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Cf. conversation https://github.com/openfoodfoundation/openfoodnetwork/pull/13251
|
|
||||||
Rails/LexicallyScopedActionFilter:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
Rails/OutputSafety:
|
|
||||||
Exclude:
|
|
||||||
- spec/**/*
|
|
||||||
|
|
||||||
Rails/RedundantActiveRecordAllMethod:
|
|
||||||
AllowedReceivers:
|
|
||||||
- ActionMailer::Preview
|
|
||||||
- ActiveSupport::TimeZone
|
|
||||||
|
|
||||||
Rails/SkipsModelValidations:
|
Rails/SkipsModelValidations:
|
||||||
AllowedMethods:
|
AllowedMethods:
|
||||||
- touch
|
- "touch"
|
||||||
- touch_all
|
- "touch_all"
|
||||||
- update_all
|
- "update_all"
|
||||||
- update_attribute
|
- "update_attribute"
|
||||||
- update_column
|
- "update_column"
|
||||||
- update_columns
|
- "update_columns"
|
||||||
|
|
||||||
Rails/UnknownEnv:
|
|
||||||
Environments:
|
|
||||||
- development
|
|
||||||
- production
|
|
||||||
- staging
|
|
||||||
- test
|
|
||||||
|
|
||||||
Rails/WhereExists:
|
|
||||||
EnforcedStyle: where # Cf. conversion https://github.com/openfoodfoundation/openfoodnetwork/pull/12363
|
|
||||||
|
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/FormatStringToken:
|
Style/StringLiterals:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/HashSyntax:
|
Style/HashSyntax:
|
||||||
@@ -163,5 +75,62 @@ Style/HashSyntax:
|
|||||||
Style/Send:
|
Style/Send:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
Style/StringLiterals:
|
Layout/MultilineMethodCallIndentation:
|
||||||
|
Enabled: true
|
||||||
|
EnforcedStyle: indented
|
||||||
|
|
||||||
|
Layout/LineLength:
|
||||||
|
Enabled: true
|
||||||
|
Max: 100
|
||||||
|
|
||||||
|
Lint/RaiseException:
|
||||||
|
Enabled: true
|
||||||
|
|
||||||
|
Lint/StructNewOverride:
|
||||||
|
Enabled: true
|
||||||
|
|
||||||
|
Naming/VariableNumber:
|
||||||
|
AllowedIdentifiers:
|
||||||
|
- street_address_1
|
||||||
|
- street_address_2
|
||||||
|
AllowedPatterns:
|
||||||
|
- _v[\d]+
|
||||||
|
|
||||||
|
Bundler/DuplicatedGem:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
## TEMPORARY/CONTESTED SETTINGS
|
||||||
|
#
|
||||||
|
# These are still to be decided upon, but recommended for inclusion by
|
||||||
|
# oeoeaio after scrutinising offenses the codebase
|
||||||
|
|
||||||
|
# Don't think this is a big issue, mostly picking up RPSEC scope definitions
|
||||||
|
# with lamdas and RSpec '.to change{}' blocks
|
||||||
|
Lint/AmbiguousBlockAssociation:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
# Heaps of offences (> 100) in specs, mostly in situations where two or more
|
||||||
|
# instances of a model are required, but only one is referenced. Difficult to
|
||||||
|
# fix without making the spec look messy or rewriting it.
|
||||||
|
# Should definitely fix at some point.
|
||||||
|
Lint/UselessAssignment:
|
||||||
|
Exclude:
|
||||||
|
- spec/**/*
|
||||||
|
|
||||||
|
Lint/MissingSuper:
|
||||||
|
Exclude:
|
||||||
|
- 'app/components/**/*'
|
||||||
|
|
||||||
|
Metrics/AbcSize:
|
||||||
|
Max: 30 # default 17
|
||||||
|
|
||||||
|
Metrics/MethodLength:
|
||||||
|
Enabled: true
|
||||||
|
Max: 25 # default 10
|
||||||
|
|
||||||
|
Metrics/PerceivedComplexity:
|
||||||
|
Enabled: true
|
||||||
|
Max: 14 # default 8
|
||||||
|
|
||||||
|
Naming/PredicateName:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|||||||
1637
.rubocop_todo.yml
1637
.rubocop_todo.yml
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
3.4.8
|
3.1.4
|
||||||
|
|||||||
19
.scss-lint.yml
Normal file
19
.scss-lint.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
scss_files: 'app/assets/stylesheets/**/*.css.scss'
|
||||||
|
|
||||||
|
exclude: 'app/assets/stylesheets/shared/**'
|
||||||
|
|
||||||
|
linters:
|
||||||
|
ImportantRule:
|
||||||
|
enabled: false
|
||||||
|
VendorPrefix:
|
||||||
|
enabled: false
|
||||||
|
LeadingZero:
|
||||||
|
enabled: false
|
||||||
|
PropertySortOrder:
|
||||||
|
enabled: false
|
||||||
|
StringQuotes:
|
||||||
|
enabled: false
|
||||||
|
DeclarationOrder:
|
||||||
|
enabled: false
|
||||||
|
NestingDepth:
|
||||||
|
enabled: false
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# .secrets file define github secrets value locally
|
|
||||||
DEPENDABOT_PR_APP_ID=123456
|
|
||||||
DEPENDABOT_PR_APP_INSTALLATION_ID=123456
|
|
||||||
DEPENDABOT_PR_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n....\n-----END RSA PRIVATE KEY-----"
|
|
||||||
23
.simplecov
23
.simplecov
@@ -1,18 +1,17 @@
|
|||||||
#!/bin/env ruby
|
#!/bin/env ruby
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
SimpleCov.start 'rails' do
|
SimpleCov.start 'rails' do
|
||||||
# The rails profile contains some filters already:
|
|
||||||
#
|
|
||||||
# - "/test/"
|
|
||||||
# - "/features/"
|
|
||||||
# - "/spec/"
|
|
||||||
# - "/autotest/"
|
|
||||||
# - /^\/config\//
|
|
||||||
# - /^\/db\//
|
|
||||||
add_filter '/bin/'
|
add_filter '/bin/'
|
||||||
add_filter '/config/' # to include engine config
|
add_filter '/config/'
|
||||||
|
add_filter '/jobs/application_job.rb'
|
||||||
|
add_filter '/schemas/'
|
||||||
|
add_filter '/lib/generators'
|
||||||
|
add_filter '/spec/'
|
||||||
|
add_filter '/vendor/'
|
||||||
|
add_filter '/public'
|
||||||
|
add_filter '/swagger'
|
||||||
add_filter '/script'
|
add_filter '/script'
|
||||||
|
add_filter '/log'
|
||||||
formatter SimpleCov::Formatter::SimpleFormatter
|
add_filter '/db'
|
||||||
|
add_filter '/lib/tasks/sample_data/'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/env ruby
|
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
--compare master
|
|
||||||
|
|
||||||
# This shouldn't be needed in undercover > 0.7.4
|
|
||||||
#
|
|
||||||
# * https://github.com/grodowski/undercover/issues/233
|
|
||||||
--exclude-files "bin/*,db/*,config/*,spec/*,engines/*/config/*,engines/*/spec/*"
|
|
||||||
118
Dockerfile
118
Dockerfile
@@ -1,34 +1,92 @@
|
|||||||
FROM ruby:3.4.8-alpine3.19 AS base
|
FROM ubuntu:20.04
|
||||||
ENV LANG=C.UTF-8 \
|
|
||||||
LC_ALL=C.UTF-8 \
|
|
||||||
TZ=Europe/London \
|
|
||||||
RAILS_ROOT=/usr/src/app \
|
|
||||||
BUNDLE_PATH=/bundles \
|
|
||||||
BUNDLE_APP_CONFIG=/bundles
|
|
||||||
RUN apk --no-cache upgrade && \
|
|
||||||
apk add --no-cache tzdata postgresql-client imagemagick imagemagick-jpeg && \
|
|
||||||
apk add --no-cache --virtual wkhtmltopdf
|
|
||||||
|
|
||||||
WORKDIR $RAILS_ROOT
|
ENV TZ Europe/London
|
||||||
|
|
||||||
# Development dependencies
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
FROM base AS development-base
|
|
||||||
RUN apk add --no-cache --virtual .build-deps \
|
|
||||||
build-base postgresql-dev git nodejs yarn && \
|
|
||||||
apk add --no-cache --virtual .dev-utils \
|
|
||||||
bash curl less vim chromium-chromedriver zlib-dev openssl-dev cmake\
|
|
||||||
readline-dev yaml-dev sqlite-dev libxml2-dev libxslt-dev libffi-dev vips-dev && \
|
|
||||||
curl -o /usr/local/bin/wait-for-it https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh && \
|
|
||||||
chmod +x /usr/local/bin/wait-for-it
|
|
||||||
|
|
||||||
# Install yarn dependencies separately for caching
|
RUN echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list
|
||||||
FROM development-base AS yarn-dependencies
|
|
||||||
COPY package.json yarn.lock ./
|
|
||||||
RUN yarn install --frozen-lockfile
|
|
||||||
|
|
||||||
# Install Ruby gems
|
# Install all the requirements
|
||||||
FROM development-base
|
RUN apt-get update && apt-get install -y \
|
||||||
COPY . $RAILS_ROOT
|
curl \
|
||||||
COPY Gemfile Gemfile.lock ./
|
git \
|
||||||
RUN bundle install --jobs "$(nproc)"
|
build-essential \
|
||||||
COPY --from=yarn-dependencies $RAILS_ROOT/node_modules ./node_modules
|
software-properties-common \
|
||||||
|
wget \
|
||||||
|
zlib1g-dev \
|
||||||
|
libreadline-dev \
|
||||||
|
libyaml-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
libxslt1-dev \
|
||||||
|
wait-for-it \
|
||||||
|
imagemagick \
|
||||||
|
unzip \
|
||||||
|
libjemalloc-dev \
|
||||||
|
libssl-dev \
|
||||||
|
ca-certificates \
|
||||||
|
gnupg
|
||||||
|
|
||||||
|
# Setup ENV variables
|
||||||
|
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:/usr/local/src/nodenv/shims:/usr/local/src/nodenv/bin:$PATH
|
||||||
|
ENV RBENV_ROOT /usr/local/src/rbenv
|
||||||
|
ENV NODENV_ROOT /usr/local/src/nodenv
|
||||||
|
ENV CONFIGURE_OPTS --disable-install-doc
|
||||||
|
ENV BUNDLE_PATH /bundles
|
||||||
|
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# trim spaces and line return from .ruby-version file
|
||||||
|
COPY .ruby-version .ruby-version.raw
|
||||||
|
RUN cat .ruby-version.raw | tr -d '\r\t ' > .ruby-version
|
||||||
|
|
||||||
|
# Install Rbenv & Ruby
|
||||||
|
RUN git clone --depth 1 https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
|
||||||
|
git clone --depth 1 https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
|
||||||
|
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh && \
|
||||||
|
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install $(cat .ruby-version) && \
|
||||||
|
rbenv global $(cat .ruby-version)
|
||||||
|
|
||||||
|
# Install Postgres
|
||||||
|
RUN sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main' >> /etc/apt/sources.list.d/pgdg.list" && \
|
||||||
|
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -yqq --no-install-recommends postgresql-client-10 libpq-dev
|
||||||
|
|
||||||
|
|
||||||
|
# trim spaces and line return from .node-version file
|
||||||
|
COPY .node-version .node-version.raw
|
||||||
|
RUN cat .node-version.raw | tr -d '\r\t ' > .node-version
|
||||||
|
|
||||||
|
# Install Node and Yarn with Nodenv
|
||||||
|
RUN git clone --depth 1 https://github.com/nodenv/nodenv.git ${NODENV_ROOT} && \
|
||||||
|
git clone --depth 1 https://github.com/nodenv/node-build.git ${NODENV_ROOT}/plugins/node-build && \
|
||||||
|
git clone --depth 1 https://github.com/pine/nodenv-yarn-install.git ${NODENV_ROOT}/plugins/nodenv-yarn-install && \
|
||||||
|
git clone --depth 1 https://github.com/nodenv/nodenv-package-rehash.git ${NODENV_ROOT}/plugins/nodenv-package-rehash && \
|
||||||
|
echo 'eval "$(nodenv init -)"' >> /etc/profile.d/nodenv.sh && \
|
||||||
|
nodenv install $(cat .node-version) && \
|
||||||
|
nodenv global $(cat .node-version)
|
||||||
|
|
||||||
|
# Install Chrome
|
||||||
|
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
|
||||||
|
sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -fy google-chrome-stable
|
||||||
|
|
||||||
|
# Install Chromedriver
|
||||||
|
RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip && \
|
||||||
|
unzip chromedriver_linux64.zip -d /usr/bin && \
|
||||||
|
chmod u+x /usr/bin/chromedriver
|
||||||
|
|
||||||
|
# Copy code and install app dependencies
|
||||||
|
COPY . /usr/src/app/
|
||||||
|
|
||||||
|
# Install Bundler
|
||||||
|
RUN ./script/install-bundler
|
||||||
|
|
||||||
|
# Install front-end dependencies
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
# Run bundler install in parallel with the amount of available CPUs
|
||||||
|
RUN bundle install --jobs="$(nproc)"
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
FROM ubuntu:20.04
|
|
||||||
|
|
||||||
ENV TZ Europe/London
|
|
||||||
|
|
||||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
|
||||||
|
|
||||||
RUN echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list
|
|
||||||
|
|
||||||
# Install all the requirements
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
build-essential \
|
|
||||||
software-properties-common \
|
|
||||||
wget \
|
|
||||||
zlib1g-dev \
|
|
||||||
libreadline-dev \
|
|
||||||
libyaml-dev \
|
|
||||||
libffi-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
libxslt1-dev \
|
|
||||||
wait-for-it \
|
|
||||||
imagemagick \
|
|
||||||
unzip \
|
|
||||||
libjemalloc-dev \
|
|
||||||
libssl-dev \
|
|
||||||
ca-certificates \
|
|
||||||
gnupg \
|
|
||||||
cmake
|
|
||||||
|
|
||||||
# Setup ENV variables
|
|
||||||
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:/usr/local/src/nodenv/shims:/usr/local/src/nodenv/bin:$PATH
|
|
||||||
ENV RBENV_ROOT /usr/local/src/rbenv
|
|
||||||
ENV NODENV_ROOT /usr/local/src/nodenv
|
|
||||||
ENV CONFIGURE_OPTS --disable-install-doc
|
|
||||||
ENV BUNDLE_PATH /bundles
|
|
||||||
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
# trim spaces and line return from .ruby-version file
|
|
||||||
COPY .ruby-version .ruby-version.raw
|
|
||||||
RUN cat .ruby-version.raw | tr -d '\r\t ' > .ruby-version
|
|
||||||
|
|
||||||
# Install Rbenv & Ruby
|
|
||||||
RUN git clone --depth 1 https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
|
|
||||||
git clone --depth 1 https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
|
|
||||||
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh && \
|
|
||||||
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install $(cat .ruby-version) && \
|
|
||||||
rbenv global $(cat .ruby-version)
|
|
||||||
|
|
||||||
# Install Postgres
|
|
||||||
RUN sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main' >> /etc/apt/sources.list.d/pgdg.list" && \
|
|
||||||
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -yqq --no-install-recommends postgresql-client-10 libpq-dev
|
|
||||||
|
|
||||||
|
|
||||||
# trim spaces and line return from .node-version file
|
|
||||||
COPY .node-version .node-version.raw
|
|
||||||
RUN cat .node-version.raw | tr -d '\r\t ' > .node-version
|
|
||||||
|
|
||||||
# Install Node and Yarn with Nodenv
|
|
||||||
RUN git clone --depth 1 https://github.com/nodenv/nodenv.git ${NODENV_ROOT} && \
|
|
||||||
git clone --depth 1 https://github.com/nodenv/node-build.git ${NODENV_ROOT}/plugins/node-build && \
|
|
||||||
git clone --depth 1 https://github.com/pine/nodenv-yarn-install.git ${NODENV_ROOT}/plugins/nodenv-yarn-install && \
|
|
||||||
git clone --depth 1 https://github.com/nodenv/nodenv-package-rehash.git ${NODENV_ROOT}/plugins/nodenv-package-rehash && \
|
|
||||||
echo 'eval "$(nodenv init -)"' >> /etc/profile.d/nodenv.sh && \
|
|
||||||
nodenv install $(cat .node-version) && \
|
|
||||||
nodenv global $(cat .node-version)
|
|
||||||
|
|
||||||
# Install Chrome
|
|
||||||
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
|
|
||||||
sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -fy google-chrome-stable
|
|
||||||
|
|
||||||
# Install Chromedriver
|
|
||||||
RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip && \
|
|
||||||
unzip chromedriver_linux64.zip -d /usr/bin && \
|
|
||||||
chmod u+x /usr/bin/chromedriver
|
|
||||||
|
|
||||||
# Copy code and install app dependencies
|
|
||||||
COPY . /usr/src/app/
|
|
||||||
|
|
||||||
# Install front-end dependencies
|
|
||||||
RUN yarn install
|
|
||||||
|
|
||||||
# Run bundler install in parallel with the amount of available CPUs
|
|
||||||
RUN bundle install --jobs="$(nproc)"
|
|
||||||
@@ -6,9 +6,28 @@ This is a general guide to setting up an Open Food Network **development environ
|
|||||||
|
|
||||||
Head to our wiki on [Learning Rails](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Learning-Rails) to find some good starting points.
|
Head to our wiki on [Learning Rails](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Learning-Rails) to find some good starting points.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
The fastest way to make it work locally is to use Docker, you only need to setup git, see the [Docker setup guide](docker/README.md).
|
||||||
|
Otherwise, for a local setup you will need:
|
||||||
|
* Ruby and bundler (check current Ruby version in [.ruby-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.ruby-version) file)
|
||||||
|
- To manage versions, it's recommended to use [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/)
|
||||||
|
* Node and yarn (check current Node version in [.node-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.node-version) file)
|
||||||
|
- [nodevn](https://github.com/nodenv/nodenv) is recommended.
|
||||||
|
* PostgreSQL database
|
||||||
|
* Redis (for background jobs)
|
||||||
|
* Chrome (for testing)
|
||||||
|
|
||||||
|
The following guides will provide OS-specific step-by-step instructions to get these requirements installed:
|
||||||
|
- [Ubuntu Setup Guide][ubuntu]
|
||||||
|
- [Debian Setup Guide][debian]
|
||||||
|
- [OSX Setup Guide][osx]
|
||||||
|
|
||||||
|
For those new to Rails, the following tutorial will help get you up to speed with configuring a [Rails environment](http://guides.rubyonrails.org/getting_started.html).
|
||||||
|
|
||||||
### Get it
|
### Get it
|
||||||
|
|
||||||
If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the `Fork` button in the top-right corner of this screen. You should then be able to use `git clone` to copy your fork onto your local machine:
|
So you have set up your local environment according to the requirements listed above. If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the `Fork` button in the top-right corner of this screen. You should then be able to use `git clone` to copy your fork onto your local machine:
|
||||||
|
|
||||||
git clone git@github.com:YOUR_GITHUB_USERNAME_HERE/openfoodnetwork.git
|
git clone git@github.com:YOUR_GITHUB_USERNAME_HERE/openfoodnetwork.git
|
||||||
|
|
||||||
@@ -24,27 +43,6 @@ Fetch the latest version of `master` from `upstream` (ie. the main repo):
|
|||||||
|
|
||||||
git fetch upstream master
|
git fetch upstream master
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
This project needs specific ruby/bundler versions as well as node/yarn specific versions. For a local setup you will need:
|
|
||||||
|
|
||||||
* Install or change your Ruby version according to the one specified at [.ruby-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.ruby-version) file.
|
|
||||||
- To manage versions, it's recommended to use [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/).
|
|
||||||
* Install [nodenv](https://github.com/nodenv/nodenv) to ensure the correct [.node-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.node-version) is used.
|
|
||||||
- [nodenv](https://github.com/nodenv/nodenv) is recommended as a node version manager.
|
|
||||||
* PostgreSQL database
|
|
||||||
* Redis (for background jobs)
|
|
||||||
* Chrome (for testing)
|
|
||||||
|
|
||||||
The following guides will provide OS-specific step-by-step instructions to get these requirements installed:
|
|
||||||
- [Ubuntu Setup Guide][ubuntu]
|
|
||||||
- [Debian Setup Guide][debian]
|
|
||||||
- [OSX Setup Guide][osx]
|
|
||||||
|
|
||||||
For those new to Rails, the following tutorial will help get you up to speed with configuring a [Rails environment](http://guides.rubyonrails.org/getting_started.html).
|
|
||||||
|
|
||||||
Another way to make it work locally would be using Docker. See the [Docker setup guide](docker/README.md).
|
|
||||||
|
|
||||||
### Get it running
|
### Get it running
|
||||||
|
|
||||||
First, you need to create the database user the app will use by manually typing the following in your terminal:
|
First, you need to create the database user the app will use by manually typing the following in your terminal:
|
||||||
@@ -55,8 +53,7 @@ sudo --login --user=postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PA
|
|||||||
|
|
||||||
This will create the "ofn" user as superuser and allowing it to create databases. If this command fails, check the [troubleshooting section](#creating-the-database) for an alternative.
|
This will create the "ofn" user as superuser and allowing it to create databases. If this command fails, check the [troubleshooting section](#creating-the-database) for an alternative.
|
||||||
|
|
||||||
Next, it is _strongly recommended_ to run the setup script:
|
Next, it is _strongly recommended_ to run the setup script.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./script/setup
|
./script/setup
|
||||||
```
|
```
|
||||||
@@ -73,7 +70,7 @@ To login as the default user, use:
|
|||||||
email: ofn@example.com
|
email: ofn@example.com
|
||||||
password: ofn123
|
password: ofn123
|
||||||
|
|
||||||
See [Locale and sample data] about loading data.
|
Seee [Locale and sample data] about loading data.
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
|||||||
74
Gemfile
74
Gemfile
@@ -1,11 +1,11 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://gem.coop'
|
source 'https://rubygems.org'
|
||||||
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
|
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
|
||||||
|
|
||||||
ruby File.read('.ruby-version').chomp
|
ruby File.read('.ruby-version').chomp
|
||||||
|
|
||||||
gem 'dotenv', require: 'dotenv/load' # Load ENV vars before other gems
|
gem 'dotenv-rails', require: 'dotenv/rails-now' # Load ENV vars before other gems
|
||||||
|
|
||||||
gem 'rails'
|
gem 'rails'
|
||||||
|
|
||||||
@@ -14,26 +14,24 @@ gem "active_storage_validations"
|
|||||||
gem "aws-sdk-s3", require: false
|
gem "aws-sdk-s3", require: false
|
||||||
gem "image_processing"
|
gem "image_processing"
|
||||||
|
|
||||||
gem 'activemerchant'
|
gem 'activemerchant', '>= 1.78.0'
|
||||||
gem 'angular-rails-templates'
|
gem 'angular-rails-templates', '>= 0.3.0'
|
||||||
gem 'ransack', '~> 4.1.0'
|
gem 'awesome_nested_set'
|
||||||
|
gem 'ransack', '~> 2.6.0'
|
||||||
gem 'responders'
|
gem 'responders'
|
||||||
gem 'shakapacker', '8.4.0'
|
gem 'rexml'
|
||||||
|
gem 'webpacker', '~> 5'
|
||||||
# Indirect dependency but we access it directly in JS specs.
|
|
||||||
# It turns out to be hard to upgrade but please do if you can.
|
|
||||||
gem 'sprockets', '~> 3.7'
|
|
||||||
|
|
||||||
gem 'i18n'
|
gem 'i18n'
|
||||||
gem 'i18n-js', '~> 3.9.0'
|
gem 'i18n-js', '~> 3.9.0'
|
||||||
gem 'rails-i18n'
|
gem 'rails-i18n'
|
||||||
|
gem 'rails_safe_tasks', '~> 1.0'
|
||||||
|
|
||||||
gem "activerecord-import"
|
gem "activerecord-import"
|
||||||
gem "db2fog", github: "openfoodfoundation/db2fog", branch: "rails-7"
|
gem "db2fog", github: "openfoodfoundation/db2fog", branch: "rails-7"
|
||||||
gem "fog-aws", "~> 2.0" # db2fog does not support v3
|
gem "fog-aws", "~> 2.0" # db2fog does not support v3
|
||||||
gem "mime-types" # required by fog
|
gem "mime-types" # required by fog
|
||||||
|
|
||||||
gem "validates_lengths_from_database"
|
|
||||||
gem "valid_email2"
|
gem "valid_email2"
|
||||||
|
|
||||||
gem "catalog", path: "./engines/catalog"
|
gem "catalog", path: "./engines/catalog"
|
||||||
@@ -43,13 +41,13 @@ gem 'web', path: './engines/web'
|
|||||||
|
|
||||||
gem "activerecord-postgresql-adapter"
|
gem "activerecord-postgresql-adapter"
|
||||||
gem "arel-helpers", "~> 2.12"
|
gem "arel-helpers", "~> 2.12"
|
||||||
gem "pg"
|
gem "pg", "~> 1.2.3"
|
||||||
|
|
||||||
gem 'acts_as_list', '1.0.4'
|
gem 'acts_as_list', '1.0.4'
|
||||||
gem 'cancancan', '~> 1.15.0'
|
gem 'cancancan', '~> 1.15.0'
|
||||||
gem 'digest'
|
gem 'digest'
|
||||||
gem 'ffaker'
|
gem 'ffaker'
|
||||||
gem 'highline'
|
gem 'highline', '2.0.3' # Necessary for the install generator
|
||||||
gem 'json'
|
gem 'json'
|
||||||
gem 'monetize', '~> 1.11'
|
gem 'monetize', '~> 1.11'
|
||||||
gem 'paranoia', '~> 2.4'
|
gem 'paranoia', '~> 2.4'
|
||||||
@@ -57,8 +55,7 @@ gem 'state_machines-activerecord'
|
|||||||
gem 'stringex', '~> 2.8.5', require: false
|
gem 'stringex', '~> 2.8.5', require: false
|
||||||
|
|
||||||
gem 'paypal-sdk-merchant', '1.117.2'
|
gem 'paypal-sdk-merchant', '1.117.2'
|
||||||
gem 'stripe', '~> 15'
|
gem 'stripe'
|
||||||
gem "taler"
|
|
||||||
|
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
gem 'devise-encryptable'
|
gem 'devise-encryptable'
|
||||||
@@ -69,14 +66,14 @@ gem 'oauth2', '~> 1.4.7' # Used for Stripe Connect
|
|||||||
|
|
||||||
gem 'datafoodconsortium-connector'
|
gem 'datafoodconsortium-connector'
|
||||||
gem 'jsonapi-serializer'
|
gem 'jsonapi-serializer'
|
||||||
gem 'pagy', '~> 9'
|
gem 'pagy', '~> 5.1'
|
||||||
|
|
||||||
gem 'rswag-api'
|
gem 'rswag-api'
|
||||||
gem 'rswag-ui'
|
gem 'rswag-ui'
|
||||||
|
|
||||||
gem 'omniauth_openid_connect'
|
gem 'omniauth_openid_connect'
|
||||||
gem 'omniauth-rails_csrf_protection'
|
gem 'omniauth-rails_csrf_protection'
|
||||||
gem 'openid_connect'
|
gem 'openid_connect', '~> 1.3'
|
||||||
|
|
||||||
gem 'angularjs-rails', '1.8.0'
|
gem 'angularjs-rails', '1.8.0'
|
||||||
gem 'bugsnag'
|
gem 'bugsnag'
|
||||||
@@ -90,29 +87,27 @@ gem "active_model_serializers", "0.8.4"
|
|||||||
gem 'activerecord-session_store'
|
gem 'activerecord-session_store'
|
||||||
gem 'acts-as-taggable-on'
|
gem 'acts-as-taggable-on'
|
||||||
gem 'angularjs-file-upload-rails', '~> 2.4.1'
|
gem 'angularjs-file-upload-rails', '~> 2.4.1'
|
||||||
gem 'bigdecimal'
|
gem 'bigdecimal', '3.0.2'
|
||||||
gem 'bootsnap', require: false
|
gem 'bootsnap', require: false
|
||||||
gem 'geocoder'
|
gem 'geocoder'
|
||||||
gem 'gmaps4rails'
|
gem 'gmaps4rails'
|
||||||
gem 'mimemagic', '> 0.3.5'
|
gem 'mimemagic', '> 0.3.5'
|
||||||
gem 'paper_trail'
|
gem 'paper_trail', '~> 12.1'
|
||||||
gem 'rack-rewrite'
|
gem 'rack-rewrite'
|
||||||
gem 'rack-timeout'
|
gem 'rack-timeout'
|
||||||
gem 'roadie-rails'
|
gem 'roadie-rails'
|
||||||
|
|
||||||
|
gem 'hiredis'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
gem 'redis'
|
gem 'redis', '>= 4.0', require: ['redis', 'redis/connection/hiredis']
|
||||||
gem 'sidekiq'
|
gem 'sidekiq'
|
||||||
gem 'sidekiq-scheduler'
|
gem 'sidekiq-scheduler'
|
||||||
|
|
||||||
gem "cable_ready"
|
gem "cable_ready", "5.0.0.rc2"
|
||||||
gem "stimulus_reflex"
|
gem "stimulus_reflex", "3.5.0.rc2"
|
||||||
|
|
||||||
gem "turbo_power"
|
|
||||||
gem "turbo-rails"
|
|
||||||
|
|
||||||
gem 'combine_pdf'
|
gem 'combine_pdf'
|
||||||
gem 'wicked_pdf', github: "openfoodfoundation/wicked_pdf", branch: "master"
|
gem 'wicked_pdf'
|
||||||
gem 'wkhtmltopdf-binary'
|
gem 'wkhtmltopdf-binary'
|
||||||
|
|
||||||
gem 'immigrant'
|
gem 'immigrant'
|
||||||
@@ -127,8 +122,7 @@ gem 'angular_rails_csrf'
|
|||||||
|
|
||||||
gem 'jquery-rails', '4.4.0'
|
gem 'jquery-rails', '4.4.0'
|
||||||
gem 'jquery-ui-rails', '~> 4.2'
|
gem 'jquery-ui-rails', '~> 4.2'
|
||||||
gem "select2-rails", github: "openfoodfoundation/select2-rails",
|
gem "select2-rails", github: "openfoodfoundation/select2-rails", branch: "v349_with_thor_v1"
|
||||||
branch: "v349_with-relaxed-dependencies"
|
|
||||||
|
|
||||||
gem 'good_migrations'
|
gem 'good_migrations'
|
||||||
|
|
||||||
@@ -139,17 +133,11 @@ gem 'flipper-ui'
|
|||||||
gem "view_component"
|
gem "view_component"
|
||||||
gem 'view_component_reflex', '3.1.14.pre9'
|
gem 'view_component_reflex', '3.1.14.pre9'
|
||||||
|
|
||||||
# mini_portile2 is needed when installing with Vargant
|
|
||||||
# https://openfoodnetwork.slack.com/archives/CEBMTRCNS/p1668439152992899
|
|
||||||
gem 'mini_portile2', '~> 2.8'
|
gem 'mini_portile2', '~> 2.8'
|
||||||
|
|
||||||
gem "faraday"
|
gem "faraday"
|
||||||
gem "private_address_check"
|
gem "private_address_check"
|
||||||
|
|
||||||
gem 'newrelic_rpm'
|
|
||||||
|
|
||||||
gem 'invisible_captcha'
|
|
||||||
|
|
||||||
group :production, :staging do
|
group :production, :staging do
|
||||||
gem 'sd_notify' # For better Systemd process management. Used by Puma.
|
gem 'sd_notify' # For better Systemd process management. Used by Puma.
|
||||||
end
|
end
|
||||||
@@ -157,7 +145,6 @@ end
|
|||||||
group :test, :development do
|
group :test, :development do
|
||||||
gem 'bullet'
|
gem 'bullet'
|
||||||
gem 'capybara'
|
gem 'capybara'
|
||||||
gem 'capybara-shadowdom'
|
|
||||||
gem 'cuprite'
|
gem 'cuprite'
|
||||||
gem 'database_cleaner', require: false
|
gem 'database_cleaner', require: false
|
||||||
gem 'debug', '>= 1.0.0'
|
gem 'debug', '>= 1.0.0'
|
||||||
@@ -168,18 +155,15 @@ group :test, :development do
|
|||||||
gem 'letter_opener', '>= 1.4.1'
|
gem 'letter_opener', '>= 1.4.1'
|
||||||
gem 'rspec-rails', ">= 3.5.2"
|
gem 'rspec-rails', ">= 3.5.2"
|
||||||
gem 'rspec-retry', require: false
|
gem 'rspec-retry', require: false
|
||||||
gem 'rspec-sql'
|
gem 'rswag-specs'
|
||||||
gem 'rswag'
|
|
||||||
gem 'shoulda-matchers'
|
gem 'shoulda-matchers'
|
||||||
gem 'stimulus_reflex_testing', github: "podia/stimulus_reflex_testing", branch: :main
|
gem 'timecop'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'pdf-reader'
|
gem 'pdf-reader'
|
||||||
gem 'puffing-billy'
|
|
||||||
gem 'rails-controller-testing'
|
gem 'rails-controller-testing'
|
||||||
gem 'simplecov', require: false
|
gem 'simplecov', require: false
|
||||||
gem 'undercover', require: false
|
|
||||||
gem 'vcr', require: false
|
gem 'vcr', require: false
|
||||||
gem 'webmock', require: false
|
gem 'webmock', require: false
|
||||||
# See spec/spec_helper.rb for instructions
|
# See spec/spec_helper.rb for instructions
|
||||||
@@ -187,22 +171,16 @@ group :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
gem 'debugger-linecache'
|
||||||
gem 'foreman'
|
gem 'foreman'
|
||||||
gem 'haml_lint', require: false
|
|
||||||
gem 'i18n-tasks'
|
|
||||||
gem 'listen'
|
gem 'listen'
|
||||||
gem 'pry'
|
gem 'pry', '~> 0.13.0'
|
||||||
gem 'query_count'
|
gem 'query_count'
|
||||||
gem 'rails-erd'
|
gem 'rails-erd'
|
||||||
gem 'rubocop'
|
gem 'rubocop'
|
||||||
gem 'rubocop-capybara'
|
|
||||||
gem 'rubocop-factory_bot'
|
|
||||||
gem 'rubocop-rails'
|
gem 'rubocop-rails'
|
||||||
gem 'rubocop-rspec'
|
|
||||||
gem 'rubocop-rspec_rails'
|
|
||||||
gem 'spring'
|
gem 'spring'
|
||||||
gem 'spring-commands-rspec'
|
gem 'spring-commands-rspec'
|
||||||
gem 'spring-commands-rubocop'
|
|
||||||
gem 'web-console'
|
gem 'web-console'
|
||||||
|
|
||||||
gem 'rack-mini-profiler', '< 3.0.0'
|
gem 'rack-mini-profiler', '< 3.0.0'
|
||||||
|
|||||||
1087
Gemfile.lock
1087
Gemfile.lock
File diff suppressed because it is too large
Load Diff
2
Procfile
2
Procfile
@@ -1,5 +1,5 @@
|
|||||||
# Foreman Procfile. Start all dev server processes with: `foreman start`
|
# Foreman Procfile. Start all dev server processes with: `foreman start`
|
||||||
|
|
||||||
rails: DEV_CACHING=true bundle exec rails s -p 3000
|
rails: DEV_CACHING=true bundle exec rails s -p 3000
|
||||||
webpack: ./bin/shakapacker-dev-server
|
webpack: ./bin/webpack-dev-server
|
||||||
sidekiq: DEV_CACHING=true bundle exec sidekiq -q mailers -q default
|
sidekiq: DEV_CACHING=true bundle exec sidekiq -q mailers -q default
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# Foreman Procfile for Docker env. Start all dev server processes with: `bundle exec foreman start -f Procfile.docker`
|
|
||||||
|
|
||||||
webpack: SHAKAPACKER_DEV_SERVER_HOST=0.0.0.0 ./bin/shakapacker-dev-server
|
|
||||||
sidekiq: DEV_CACHING=true bundle exec sidekiq -q mailers -q default
|
|
||||||
rails: SHAKAPACKER_DEV_SERVER_HOST=0.0.0.0 DEV_CACHING=true bundle exec rails s -p 3000 -b 0.0.0.0
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
[](https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/build.yml)
|
[](https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/build.yml)
|
||||||
|
[](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork)
|
||||||
|
|
||||||
# Open Food Network
|
# Open Food Network
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ We also have a [Super Admin Guide][super-admin-guide] to help with configuration
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
If you'd like to help out with testing, please introduce yourself on the #testing channel on [Slack][slack-invite]. Also, do have a look in our [Welcome New QAs board][welcome-qa] for some good first issues, both on manual and automated testing (RSpec/Capybara).
|
If you'd like to help out with testing, please introduce yourself on the #testing channel on [Slack][slack-invite] and download the [ZenHub browser extension][zenhub] to view the development pipeline. Also, do have a look in our [Welcome New QAs board][welcome-qa] for some good first issues, both on manual and automated testing (RSpec/Capybara).
|
||||||
|
|
||||||
We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. BrowserStack provides open source projects with unlimited and free of charge accounts. A big thanks to them!
|
We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. BrowserStack provides open source projects with unlimited and free of charge accounts. A big thanks to them!
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ We use [KnapsackPro](https://knapsackpro.com/) for optimal parallelisation of ou
|
|||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright (c) 2012 - 2024 Open Food Foundation, released under the AGPL licence.
|
Copyright (c) 2012 - 2022 Open Food Foundation, released under the AGPL licence.
|
||||||
|
|
||||||
[survey]: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit#
|
[survey]: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit#
|
||||||
[slack-invite]: https://join.slack.com/t/openfoodnetwork/shared_invite/zt-9sjkjdlu-r02kUMP1zbrTgUhZhYPF~A
|
[slack-invite]: https://join.slack.com/t/openfoodnetwork/shared_invite/zt-9sjkjdlu-r02kUMP1zbrTgUhZhYPF~A
|
||||||
@@ -53,3 +54,4 @@ Copyright (c) 2012 - 2024 Open Food Foundation, released under the AGPL licence.
|
|||||||
[super-admin-guide]: https://ofn-user-guide.gitbook.io/ofn-super-admin-guide
|
[super-admin-guide]: https://ofn-user-guide.gitbook.io/ofn-super-admin-guide
|
||||||
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/5
|
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/5
|
||||||
[welcome-qa]: https://github.com/orgs/openfoodfoundation/projects/6
|
[welcome-qa]: https://github.com/orgs/openfoodfoundation/projects/6
|
||||||
|
[zenhub]: https://www.zenhub.com/extension
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
//= require jquery.ui.all
|
//= require jquery.ui.all
|
||||||
//= require jquery.powertip
|
//= require jquery.powertip
|
||||||
//= require jquery.cookie
|
//= require jquery.cookie
|
||||||
|
//= require jquery.jstree/jquery.jstree
|
||||||
//= require jquery.vAlign
|
//= require jquery.vAlign
|
||||||
//= require angular
|
//= require angular
|
||||||
//= require angular-resource
|
//= require angular-resource
|
||||||
//= require angular-animate
|
//= require angular-animate
|
||||||
//= require angular-sanitize
|
|
||||||
//= require angularjs-file-upload
|
//= require angularjs-file-upload
|
||||||
//= require ../shared/ng-infinite-scroll.min.js
|
//= require ../shared/ng-infinite-scroll.min.js
|
||||||
//= require ../shared/ng-tags-input.min.js
|
//= require ../shared/ng-tags-input.min.js
|
||||||
@@ -61,11 +61,19 @@
|
|||||||
//= require ./variant_overrides/variant_overrides
|
//= require ./variant_overrides/variant_overrides
|
||||||
|
|
||||||
// text, dates and translations
|
// text, dates and translations
|
||||||
|
//= require textAngular-rangy.min.js
|
||||||
|
// This replaces angular-sanitize. We should include only one.
|
||||||
|
// https://github.com/textAngular/textAngular#where-to-get-it
|
||||||
|
//= require textAngular-sanitize.min.js
|
||||||
|
//= require textAngular.min.js
|
||||||
//= require i18n/translations
|
//= require i18n/translations
|
||||||
//= require darkswarm/i18n.translate.js
|
//= require darkswarm/i18n.translate.js
|
||||||
|
|
||||||
// foundation
|
// foundation
|
||||||
//= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
|
//= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
|
||||||
|
|
||||||
|
// LocalStorage
|
||||||
|
//= require ../shared/angular-local-storage.js
|
||||||
|
|
||||||
// requires the rest of the JS code in this folder
|
// requires the rest of the JS code in this folder
|
||||||
//= require_tree .
|
//= require_tree .
|
||||||
|
|||||||
406
app/assets/javascripts/admin/bulk_product_update.js.coffee
Normal file
406
app/assets/javascripts/admin/bulk_product_update.js.coffee
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, $location, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor, SortOptions, ErrorsParser, ProductFiltersUrl) ->
|
||||||
|
$scope.StatusMessage = StatusMessage
|
||||||
|
|
||||||
|
$scope.columns = Columns.columns
|
||||||
|
|
||||||
|
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||||
|
|
||||||
|
$scope.RequestMonitor = RequestMonitor
|
||||||
|
$scope.pagination = BulkProducts.pagination
|
||||||
|
$scope.per_page_options = [
|
||||||
|
{id: 15, name: t('js.admin.orders.index.per_page', results: 15)},
|
||||||
|
{id: 50, name: t('js.admin.orders.index.per_page', results: 50)},
|
||||||
|
{id: 100, name: t('js.admin.orders.index.per_page', results: 100)}
|
||||||
|
]
|
||||||
|
|
||||||
|
$scope.q = {
|
||||||
|
producerFilter: ""
|
||||||
|
categoryFilter: ""
|
||||||
|
importDateFilter: ""
|
||||||
|
query: ""
|
||||||
|
sorting: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.sorting = "name asc"
|
||||||
|
$scope.producers = producers
|
||||||
|
$scope.taxons = Taxons.all
|
||||||
|
$scope.tax_categories = tax_categories
|
||||||
|
$scope.page = 1
|
||||||
|
$scope.per_page = 15
|
||||||
|
$scope.products = BulkProducts.products
|
||||||
|
$scope.DisplayProperties = DisplayProperties
|
||||||
|
|
||||||
|
$scope.sortOptions = SortOptions
|
||||||
|
|
||||||
|
$scope.initialise = ->
|
||||||
|
$scope.q = ProductFiltersUrl.loadFromUrl($location.search())
|
||||||
|
$scope.fetchProducts()
|
||||||
|
|
||||||
|
$scope.$watchCollection '[q.query, q.producerFilter, q.categoryFilter, q.importDateFilter, per_page]', ->
|
||||||
|
$scope.page = 1 # Reset page when changing filters for new search
|
||||||
|
|
||||||
|
$scope.changePage = (newPage) ->
|
||||||
|
$scope.page = newPage
|
||||||
|
$scope.fetchProducts()
|
||||||
|
|
||||||
|
$scope.fetchProducts = ->
|
||||||
|
removeClearedValues()
|
||||||
|
params = {
|
||||||
|
'q[name_cont]': $scope.q.query,
|
||||||
|
'q[supplier_id_eq]': $scope.q.producerFilter,
|
||||||
|
'q[primary_taxon_id_eq]': $scope.q.categoryFilter,
|
||||||
|
'q[s]': $scope.sorting,
|
||||||
|
import_date: $scope.q.importDateFilter,
|
||||||
|
page: $scope.page,
|
||||||
|
per_page: $scope.per_page
|
||||||
|
}
|
||||||
|
RequestMonitor.load(BulkProducts.fetch(params).$promise).then ->
|
||||||
|
# update url with the filters used
|
||||||
|
$location.search(ProductFiltersUrl.generate($scope.q))
|
||||||
|
$scope.resetProducts()
|
||||||
|
|
||||||
|
removeClearedValues = ->
|
||||||
|
delete $scope.q.producerFilter if $scope.q.producerFilter == "0"
|
||||||
|
delete $scope.q.categoryFilter if $scope.q.categoryFilter == "0"
|
||||||
|
delete $scope.q.importDateFilter if $scope.q.importDateFilter == "0"
|
||||||
|
|
||||||
|
$timeout ->
|
||||||
|
if $scope.showLatestImport
|
||||||
|
$scope.q.importDateFilter = $scope.importDates[1].id
|
||||||
|
|
||||||
|
$scope.resetProducts = ->
|
||||||
|
DirtyProducts.clear()
|
||||||
|
StatusMessage.clear()
|
||||||
|
|
||||||
|
$scope.updateOnHand = (product) ->
|
||||||
|
on_demand_variants = []
|
||||||
|
if product.variants
|
||||||
|
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
|
||||||
|
|
||||||
|
unless product.on_demand || on_demand_variants.length > 0
|
||||||
|
product.on_hand = $scope.onHand(product)
|
||||||
|
|
||||||
|
|
||||||
|
$scope.onHand = (product) ->
|
||||||
|
onHand = 0
|
||||||
|
if product.hasOwnProperty("variants") and product.variants instanceof Object
|
||||||
|
for id, variant of product.variants
|
||||||
|
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
|
||||||
|
else
|
||||||
|
onHand = "error"
|
||||||
|
onHand
|
||||||
|
|
||||||
|
$scope.shiftTab = (tab) ->
|
||||||
|
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
|
||||||
|
tab.visible = !tab.visible
|
||||||
|
$scope.visibleTab = tab
|
||||||
|
|
||||||
|
$scope.resetSelectFilters = ->
|
||||||
|
$scope.q.query = ""
|
||||||
|
$scope.q.producerFilter = "0"
|
||||||
|
$scope.q.categoryFilter = "0"
|
||||||
|
$scope.q.importDateFilter = "0"
|
||||||
|
$scope.fetchProducts()
|
||||||
|
|
||||||
|
$scope.$watch 'sortOptions', (sort) ->
|
||||||
|
return unless sort && sort.predicate != ""
|
||||||
|
|
||||||
|
$scope.sorting = sort.getSortingExpr(defaultDirection: "asc")
|
||||||
|
$scope.fetchProducts()
|
||||||
|
, true
|
||||||
|
|
||||||
|
confirm_unsaved_changes = () ->
|
||||||
|
(DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
|
||||||
|
|
||||||
|
editProductUrl = (product, variant) ->
|
||||||
|
"/admin/products/" + product.id + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||||
|
|
||||||
|
$scope.editWarn = (product, variant) ->
|
||||||
|
if confirm_unsaved_changes()
|
||||||
|
$window.location.href = ProductFiltersUrl.buildUrl(editProductUrl(product, variant), $scope.q)
|
||||||
|
|
||||||
|
$scope.toggleShowAllVariants = ->
|
||||||
|
showVariants = !DisplayProperties.showVariants 0
|
||||||
|
$scope.products.forEach (product) ->
|
||||||
|
DisplayProperties.setShowVariants product.id, showVariants
|
||||||
|
DisplayProperties.setShowVariants 0, showVariants
|
||||||
|
|
||||||
|
$scope.addVariant = (product) ->
|
||||||
|
product.variants.push
|
||||||
|
id: $scope.nextVariantId()
|
||||||
|
unit_value: null
|
||||||
|
unit_description: null
|
||||||
|
on_demand: false
|
||||||
|
display_as: null
|
||||||
|
display_name: null
|
||||||
|
on_hand: null
|
||||||
|
price: null
|
||||||
|
DisplayProperties.setShowVariants product.id, true
|
||||||
|
|
||||||
|
|
||||||
|
$scope.nextVariantId = ->
|
||||||
|
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
|
||||||
|
$scope.variantIdCounter -= 1
|
||||||
|
$scope.variantIdCounter
|
||||||
|
|
||||||
|
$scope.deleteProduct = (product) ->
|
||||||
|
if confirm(t('are_you_sure'))
|
||||||
|
$http(
|
||||||
|
method: "DELETE"
|
||||||
|
url: "/api/v0/products/" + product.id
|
||||||
|
).then (response) ->
|
||||||
|
$scope.products.splice $scope.products.indexOf(product), 1
|
||||||
|
DirtyProducts.deleteProduct product.id
|
||||||
|
$scope.displayDirtyProducts()
|
||||||
|
|
||||||
|
|
||||||
|
$scope.deleteVariant = (product, variant) ->
|
||||||
|
if product.variants.length > 1
|
||||||
|
if !$scope.variantSaved(variant)
|
||||||
|
$scope.removeVariant(product, variant)
|
||||||
|
else
|
||||||
|
if confirm(t("are_you_sure"))
|
||||||
|
$http(
|
||||||
|
method: "DELETE"
|
||||||
|
url: "/api/v0/products/" + product.id + "/variants/" + variant.id
|
||||||
|
).then (response) ->
|
||||||
|
$scope.removeVariant(product, variant)
|
||||||
|
else
|
||||||
|
alert(t("delete_product_variant"))
|
||||||
|
|
||||||
|
$scope.removeVariant = (product, variant) ->
|
||||||
|
product.variants.splice product.variants.indexOf(variant), 1
|
||||||
|
DirtyProducts.deleteVariant product.id, variant.id
|
||||||
|
$scope.displayDirtyProducts()
|
||||||
|
|
||||||
|
|
||||||
|
$scope.cloneProduct = (product) ->
|
||||||
|
BulkProducts.cloneProduct product
|
||||||
|
|
||||||
|
$scope.hasVariants = (product) ->
|
||||||
|
product.variants.length > 0
|
||||||
|
|
||||||
|
|
||||||
|
$scope.hasUnit = (product) ->
|
||||||
|
product.variant_unit_with_scale?
|
||||||
|
|
||||||
|
|
||||||
|
$scope.variantSaved = (variant) ->
|
||||||
|
variant.hasOwnProperty('id') && variant.id > 0
|
||||||
|
|
||||||
|
|
||||||
|
$scope.hasOnDemandVariants = (product) ->
|
||||||
|
(variant for id, variant of product.variants when variant.on_demand).length > 0
|
||||||
|
|
||||||
|
|
||||||
|
$scope.submitProducts = ->
|
||||||
|
# Pack pack $scope.products, so they will match the list returned from the server,
|
||||||
|
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
|
||||||
|
$scope.packProduct product for id, product of $scope.products
|
||||||
|
$scope.packProduct product for id, product of DirtyProducts.all()
|
||||||
|
|
||||||
|
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
|
||||||
|
if productsToSubmit.length > 0
|
||||||
|
$scope.updateProducts productsToSubmit # Don't submit an empty list
|
||||||
|
else
|
||||||
|
StatusMessage.display 'alert', t("products_change")
|
||||||
|
|
||||||
|
|
||||||
|
$scope.updateProducts = (productsToSubmit) ->
|
||||||
|
$scope.displayUpdating()
|
||||||
|
$http(
|
||||||
|
method: "POST"
|
||||||
|
url: "/admin/products/bulk_update"
|
||||||
|
data:
|
||||||
|
products: productsToSubmit
|
||||||
|
filters:
|
||||||
|
'q[name_cont]': $scope.q.query
|
||||||
|
'q[supplier_id_eq]': $scope.q.producerFilter
|
||||||
|
'q[primary_taxon_id_eq]': $scope.q.categoryFilter
|
||||||
|
'q[s]': $scope.sorting
|
||||||
|
import_date: $scope.q.importDateFilter
|
||||||
|
page: $scope.page
|
||||||
|
per_page: $scope.per_page
|
||||||
|
).then((response) ->
|
||||||
|
DirtyProducts.clear()
|
||||||
|
BulkProducts.updateVariantLists(response.data.products || [])
|
||||||
|
$timeout -> $scope.displaySuccess()
|
||||||
|
).catch (response) ->
|
||||||
|
if response.status == 400 && response.data.errors?
|
||||||
|
errorsString = ErrorsParser.toString(response.data.errors, response.status)
|
||||||
|
$scope.displayFailure t("products_update_error") + "\n" + errorsString
|
||||||
|
else
|
||||||
|
$scope.displayFailure t("products_update_error_data") + response.status
|
||||||
|
|
||||||
|
$scope.cancel = (destination) ->
|
||||||
|
$window.location = destination
|
||||||
|
|
||||||
|
$scope.packProduct = (product) ->
|
||||||
|
if product.variant_unit_with_scale
|
||||||
|
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
|
||||||
|
if match
|
||||||
|
product.variant_unit = match[1]
|
||||||
|
product.variant_unit_scale = parseFloat(match[2])
|
||||||
|
else
|
||||||
|
product.variant_unit = product.variant_unit_with_scale
|
||||||
|
product.variant_unit_scale = null
|
||||||
|
else
|
||||||
|
product.variant_unit = product.variant_unit_scale = null
|
||||||
|
|
||||||
|
$scope.packVariant product, product.master if product.master
|
||||||
|
|
||||||
|
if product.variants
|
||||||
|
for id, variant of product.variants
|
||||||
|
$scope.packVariant product, variant
|
||||||
|
|
||||||
|
|
||||||
|
$scope.packVariant = (product, variant) ->
|
||||||
|
if variant.hasOwnProperty("unit_value_with_description")
|
||||||
|
match = variant.unit_value_with_description.match(/^([\d\.\,]+(?= |$)|)( |)(.*)$/)
|
||||||
|
if match
|
||||||
|
product = BulkProducts.find product.id
|
||||||
|
variant.unit_value = parseFloat(match[1].replace(",", "."))
|
||||||
|
variant.unit_value = null if isNaN(variant.unit_value)
|
||||||
|
if variant.unit_value && product.variant_unit_scale
|
||||||
|
variant.unit_value = parseFloat(window.bigDecimal.multiply(variant.unit_value, product.variant_unit_scale, 2))
|
||||||
|
variant.unit_description = match[3]
|
||||||
|
|
||||||
|
$scope.incrementLimit = ->
|
||||||
|
if $scope.limit < $scope.products.length
|
||||||
|
$scope.limit = $scope.limit + 5
|
||||||
|
|
||||||
|
|
||||||
|
$scope.displayUpdating = ->
|
||||||
|
StatusMessage.display 'progress', t("saving")
|
||||||
|
|
||||||
|
|
||||||
|
$scope.displaySuccess = ->
|
||||||
|
StatusMessage.display 'success',t("products_changes_saved")
|
||||||
|
$scope.bulk_product_form.$setPristine()
|
||||||
|
|
||||||
|
|
||||||
|
$scope.displayFailure = (failMessage) ->
|
||||||
|
StatusMessage.display 'failure', t("products_update_error_msg") + " #{failMessage}"
|
||||||
|
|
||||||
|
|
||||||
|
$scope.displayDirtyProducts = ->
|
||||||
|
count = DirtyProducts.count()
|
||||||
|
switch count
|
||||||
|
when 0 then StatusMessage.clear()
|
||||||
|
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
|
||||||
|
else StatusMessage.display 'notice', t("products_unsaved", n: count)
|
||||||
|
|
||||||
|
|
||||||
|
filterSubmitProducts = (productsToFilter) ->
|
||||||
|
filteredProducts = []
|
||||||
|
if productsToFilter instanceof Object
|
||||||
|
angular.forEach productsToFilter, (product) ->
|
||||||
|
if product.hasOwnProperty("id")
|
||||||
|
filteredProduct = {id: product.id}
|
||||||
|
filteredVariants = []
|
||||||
|
filteredMaster = null
|
||||||
|
hasUpdatableProperty = false
|
||||||
|
|
||||||
|
if product.hasOwnProperty("variants")
|
||||||
|
angular.forEach product.variants, (variant) ->
|
||||||
|
result = filterSubmitVariant variant
|
||||||
|
filteredVariant = result.filteredVariant
|
||||||
|
variantHasUpdatableProperty = result.hasUpdatableProperty
|
||||||
|
filteredVariants.push filteredVariant if variantHasUpdatableProperty
|
||||||
|
|
||||||
|
if product.master?.hasOwnProperty("unit_value")
|
||||||
|
filteredMaster ?= { id: product.master.id }
|
||||||
|
filteredMaster.unit_value = product.master.unit_value
|
||||||
|
if product.master?.hasOwnProperty("unit_description")
|
||||||
|
filteredMaster ?= { id: product.master.id }
|
||||||
|
filteredMaster.unit_description = product.master.unit_description
|
||||||
|
if product.master?.hasOwnProperty("display_as")
|
||||||
|
filteredMaster ?= { id: product.master.id }
|
||||||
|
filteredMaster.display_as = product.master.display_as
|
||||||
|
|
||||||
|
if product.hasOwnProperty("sku")
|
||||||
|
filteredProduct.sku = product.sku
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("name")
|
||||||
|
filteredProduct.name = product.name
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("producer_id")
|
||||||
|
filteredProduct.supplier_id = product.producer_id
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("price")
|
||||||
|
filteredProduct.price = product.price
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("variant_unit_with_scale")
|
||||||
|
filteredProduct.variant_unit = product.variant_unit
|
||||||
|
filteredProduct.variant_unit_scale = product.variant_unit_scale
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("variant_unit_name")
|
||||||
|
filteredProduct.variant_unit_name = product.variant_unit_name
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present
|
||||||
|
filteredProduct.on_hand = product.on_hand
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present
|
||||||
|
filteredProduct.on_demand = product.on_demand
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("category_id")
|
||||||
|
filteredProduct.primary_taxon_id = product.category_id
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("tax_category_id")
|
||||||
|
filteredProduct.tax_category_id = product.tax_category_id
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if product.hasOwnProperty("inherits_properties")
|
||||||
|
filteredProduct.inherits_properties = product.inherits_properties
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if filteredMaster?
|
||||||
|
filteredProduct.master_attributes = filteredMaster
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if filteredVariants.length > 0 # Note that the name of the property changes to enable mass assignment of variants attributes with rails
|
||||||
|
filteredProduct.variants_attributes = filteredVariants
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
filteredProducts.push filteredProduct if hasUpdatableProperty
|
||||||
|
|
||||||
|
filteredProducts
|
||||||
|
|
||||||
|
|
||||||
|
filterSubmitVariant = (variant) ->
|
||||||
|
hasUpdatableProperty = false
|
||||||
|
filteredVariant = {}
|
||||||
|
if not variant.deleted_at? and variant.hasOwnProperty("id")
|
||||||
|
filteredVariant.id = variant.id unless variant.id <= 0
|
||||||
|
if variant.hasOwnProperty("sku")
|
||||||
|
filteredVariant.sku = variant.sku
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if variant.hasOwnProperty("on_hand")
|
||||||
|
filteredVariant.on_hand = variant.on_hand
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if variant.hasOwnProperty("on_demand")
|
||||||
|
filteredVariant.on_demand = variant.on_demand
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if variant.hasOwnProperty("price")
|
||||||
|
filteredVariant.price = variant.price
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if variant.hasOwnProperty("unit_value")
|
||||||
|
filteredVariant.unit_value = variant.unit_value
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if variant.hasOwnProperty("unit_description")
|
||||||
|
filteredVariant.unit_description = variant.unit_description
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if variant.hasOwnProperty("display_name")
|
||||||
|
filteredVariant.display_name = variant.display_name
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
if variant.hasOwnProperty("display_as")
|
||||||
|
filteredVariant.display_as = variant.display_as
|
||||||
|
hasUpdatableProperty = true
|
||||||
|
{filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty}
|
||||||
|
|
||||||
|
|
||||||
|
toObjectWithIDKeys = (array) ->
|
||||||
|
object = {}
|
||||||
|
|
||||||
|
for i of array
|
||||||
|
if array[i] instanceof Object and array[i].hasOwnProperty("id")
|
||||||
|
object[array[i].id] = angular.copy(array[i])
|
||||||
|
object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array
|
||||||
|
|
||||||
|
object
|
||||||
@@ -11,9 +11,6 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt
|
|||||||
$scope.confirmRefresh = (event) ->
|
$scope.confirmRefresh = (event) ->
|
||||||
event.preventDefault() unless pendingChanges.unsavedCount() == 0 || confirm(t("unsaved_changes_warning"))
|
event.preventDefault() unless pendingChanges.unsavedCount() == 0 || confirm(t("unsaved_changes_warning"))
|
||||||
|
|
||||||
$scope.hasUnsavedChanges = ->
|
|
||||||
pendingChanges.yes()
|
|
||||||
|
|
||||||
$scope.$watch "shop_id", ->
|
$scope.$watch "shop_id", ->
|
||||||
if $scope.shop_id?
|
if $scope.shop_id?
|
||||||
CurrentShop.shop = $filter('filter')($scope.shops, {id: parseInt($scope.shop_id)}, true)[0]
|
CurrentShop.shop = $filter('filter')($scope.shops, {id: parseInt($scope.shop_id)}, true)[0]
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ angular.module("ofn.admin").directive "ofnDisplayAs", (OptionValueNamer) ->
|
|||||||
scope.$watchCollection ->
|
scope.$watchCollection ->
|
||||||
return [
|
return [
|
||||||
scope.$eval(attrs.ofnDisplayAs).unit_value_with_description
|
scope.$eval(attrs.ofnDisplayAs).unit_value_with_description
|
||||||
scope.variant.variant_unit_name
|
scope.product.variant_unit_name
|
||||||
scope.variant.variant_unit_with_scale
|
scope.product.variant_unit_with_scale
|
||||||
]
|
]
|
||||||
, ->
|
, ->
|
||||||
[variant_unit, variant_unit_scale] = productUnitProperties()
|
[variant_unit, variant_unit_scale] = productUnitProperties()
|
||||||
@@ -13,21 +13,22 @@ angular.module("ofn.admin").directive "ofnDisplayAs", (OptionValueNamer) ->
|
|||||||
variant_object =
|
variant_object =
|
||||||
unit_value: unit_value
|
unit_value: unit_value
|
||||||
unit_description: unit_description
|
unit_description: unit_description
|
||||||
variant_unit_scale: variant_unit_scale
|
product:
|
||||||
variant_unit: variant_unit
|
variant_unit_scale: variant_unit_scale
|
||||||
variant_unit_name: scope.variant.variant_unit_name
|
variant_unit: variant_unit
|
||||||
|
variant_unit_name: scope.product.variant_unit_name
|
||||||
|
|
||||||
scope.placeholder_text = new OptionValueNamer(variant_object).name()
|
scope.placeholder_text = new OptionValueNamer(variant_object).name()
|
||||||
|
|
||||||
productUnitProperties = ->
|
productUnitProperties = ->
|
||||||
# get relevant product properties
|
# get relevant product properties
|
||||||
if scope.variant.variant_unit_with_scale?
|
if scope.product.variant_unit_with_scale?
|
||||||
match = scope.variant.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
|
match = scope.product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
|
||||||
if match
|
if match
|
||||||
variant_unit = match[1]
|
variant_unit = match[1]
|
||||||
variant_unit_scale = parseFloat(match[2])
|
variant_unit_scale = parseFloat(match[2])
|
||||||
else
|
else
|
||||||
variant_unit = scope.variant.variant_unit_with_scale
|
variant_unit = scope.product.variant_unit_with_scale
|
||||||
variant_unit_scale = null
|
variant_unit_scale = null
|
||||||
else
|
else
|
||||||
variant_unit = variant_unit_scale = null
|
variant_unit = variant_unit_scale = null
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
angular.module("ofn.admin").directive "ofnMaintainUnitScale", ->
|
||||||
|
require: "ngModel"
|
||||||
|
link: (scope, element, attrs, ngModel) ->
|
||||||
|
scope.$watch 'product.variant_unit_with_scale', (newValue, oldValue) ->
|
||||||
|
if not (oldValue == newValue)
|
||||||
|
# Triggers track-variant directive to track the unit_value, so that changes to the unit are passed to the server
|
||||||
|
ngModel.$setViewValue ngModel.$viewValue
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
angular.module("ofn.admin").directive "ofnTrackMaster", (DirtyProducts) ->
|
||||||
|
require: "ngModel"
|
||||||
|
link: (scope, element, attrs, ngModel) ->
|
||||||
|
ngModel.$parsers.push (viewValue) ->
|
||||||
|
if ngModel.$dirty
|
||||||
|
DirtyProducts.addMasterProperty scope.product.id, scope.product.master.id, attrs.ofnTrackMaster, viewValue
|
||||||
|
scope.displayDirtyProducts()
|
||||||
|
viewValue
|
||||||
@@ -1 +1 @@
|
|||||||
angular.module("admin.enterprise_groups", ["admin.side_menu", "admin.users", "ngSanitize"])
|
angular.module("admin.enterprise_groups", ["admin.side_menu", "admin.users", "textAngular"])
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ angular.module("admin.enterprises")
|
|||||||
else
|
else
|
||||||
alert ("#{manager.email}" + " " + t("is_already_manager"))
|
alert ("#{manager.email}" + " " + t("is_already_manager"))
|
||||||
|
|
||||||
|
$scope.removeLogo = ->
|
||||||
|
$scope.performEnterpriseAction("removeLogo", "immediate_logo_removal_warning", "removed_logo_successfully")
|
||||||
|
|
||||||
|
$scope.removePromoImage = ->
|
||||||
|
$scope.performEnterpriseAction("removePromoImage", "immediate_promo_image_removal_warning", "removed_promo_image_successfully")
|
||||||
|
|
||||||
$scope.performEnterpriseAction = (enterpriseActionName, warning_message_key, success_message_key) ->
|
$scope.performEnterpriseAction = (enterpriseActionName, warning_message_key, success_message_key) ->
|
||||||
return unless confirm($scope.translation(warning_message_key))
|
return unless confirm($scope.translation(warning_message_key))
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,24 @@ angular.module("admin.enterprises", [
|
|||||||
"admin.utils",
|
"admin.utils",
|
||||||
"admin.shippingMethods",
|
"admin.shippingMethods",
|
||||||
"admin.users",
|
"admin.users",
|
||||||
|
"textAngular",
|
||||||
"admin.side_menu",
|
"admin.side_menu",
|
||||||
"admin.taxons",
|
"admin.taxons",
|
||||||
'admin.indexUtils',
|
'admin.indexUtils',
|
||||||
|
'admin.tagRules',
|
||||||
'admin.dropdown',
|
'admin.dropdown',
|
||||||
'ngSanitize']
|
'ngSanitize']
|
||||||
)
|
)
|
||||||
|
# For more options: https://github.com/textAngular/textAngular/blob/master/src/textAngularSetup.js
|
||||||
|
.config [
|
||||||
|
'$provide', ($provide) ->
|
||||||
|
$provide.decorator 'taTranslations', [
|
||||||
|
'$delegate'
|
||||||
|
(taTranslations) ->
|
||||||
|
taTranslations.insertLink = {
|
||||||
|
tooltip: t('admin.enterprises.form.shop_preferences.shopfront_message_link_tooltip'),
|
||||||
|
dialogPrompt: t('admin.enterprises.form.shop_preferences.shopfront_message_link_prompt')
|
||||||
|
}
|
||||||
|
taTranslations
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|||||||
@@ -4,16 +4,21 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
|
|||||||
type: "@objForUpdate"
|
type: "@objForUpdate"
|
||||||
attr: "@attrForUpdate"
|
attr: "@attrForUpdate"
|
||||||
link: (scope, element, attrs) ->
|
link: (scope, element, attrs) ->
|
||||||
scope.savedValue = scope.object()[scope.attr] || ""
|
scope.savedValue = scope.object()[scope.attr]
|
||||||
|
|
||||||
scope.$watch "object().#{scope.attr}", (value) ->
|
scope.$watch "object().#{scope.attr}", (value) ->
|
||||||
strValue = value || ""
|
if value == scope.savedValue
|
||||||
if strValue == scope.savedValue
|
|
||||||
pendingChanges.remove(scope.object().id, scope.attr)
|
pendingChanges.remove(scope.object().id, scope.attr)
|
||||||
scope.clear()
|
scope.clear()
|
||||||
else
|
else
|
||||||
|
change =
|
||||||
|
object: scope.object()
|
||||||
|
type: scope.type
|
||||||
|
attr: scope.attr
|
||||||
|
value: if value? then value else ""
|
||||||
|
scope: scope
|
||||||
scope.pending()
|
scope.pending()
|
||||||
addPendingChange(scope.attr, strValue)
|
pendingChanges.add(scope.object().id, scope.attr, change)
|
||||||
|
|
||||||
scope.reset = (value) ->
|
scope.reset = (value) ->
|
||||||
scope.savedValue = value
|
scope.savedValue = value
|
||||||
@@ -29,30 +34,3 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
|
|||||||
|
|
||||||
scope.clear = ->
|
scope.clear = ->
|
||||||
switchClass( element, "", ["update-pending", "update-error", "update-success"], false )
|
switchClass( element, "", ["update-pending", "update-error", "update-success"], false )
|
||||||
|
|
||||||
# When a list of customer is filtered and we removed the "filtered value" from a customer, we
|
|
||||||
# want to make sure the customer is updated. IE. filtering by tag, and removing said tag.
|
|
||||||
# Deleting the "filtered value" from a customer will remove the customer entry, thus
|
|
||||||
# removing "objForUpdate" directive from the active scope. That means $watch won't pick up
|
|
||||||
# the attribute changed.
|
|
||||||
# To ensure the customer is still updated, we check on the $destroy event to see if
|
|
||||||
# the attribute has changed, if so we queue up the change.
|
|
||||||
scope.$on '$destroy', (value) ->
|
|
||||||
currentValue = scope.object()[scope.attr] || ""
|
|
||||||
|
|
||||||
# No update
|
|
||||||
return if currentValue is scope.savedValue
|
|
||||||
|
|
||||||
# Queuing up change
|
|
||||||
addPendingChange(scope.attr, currentValue)
|
|
||||||
|
|
||||||
# private
|
|
||||||
|
|
||||||
addPendingChange = (attr, value) ->
|
|
||||||
change =
|
|
||||||
object: scope.object()
|
|
||||||
type: scope.type
|
|
||||||
attr: attr
|
|
||||||
value: value
|
|
||||||
scope: scope
|
|
||||||
pendingChanges.add(scope.object().id, attr, change)
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
# Used like a regular angular filter where an object is passed
|
# Used like a regular angular filter where an object is passed
|
||||||
# Adds the additional special case that a value of 0 for the filter
|
# Adds the additional special case that a value of 0 for the filter
|
||||||
# acts as a bypass for that particular attribute
|
# acts as a bypass for that particular attribute
|
||||||
|
|
||||||
# NOTE the name doesn't reflect what the filter does, it only fiters on the variant.producer_id
|
|
||||||
angular.module("admin.indexUtils").filter "attrFilter", ($filter) ->
|
angular.module("admin.indexUtils").filter "attrFilter", ($filter) ->
|
||||||
return (objects, filters) ->
|
return (objects, filters) ->
|
||||||
filter = filters["producer_id"]
|
Object.keys(filters).reduce (filtered, attr) ->
|
||||||
|
filter = filters[attr]
|
||||||
return objects if !filter? || filter == 0
|
return filtered if !filter? || filter == 0
|
||||||
|
return $filter('filter')(filtered, (object) ->
|
||||||
return $filter('filter')(objects, (product) ->
|
object[attr] == filter
|
||||||
for variant in product.variants
|
)
|
||||||
return true if variant["producer_id"] == filter
|
, objects
|
||||||
false
|
|
||||||
, true)
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, $injec
|
|||||||
savePreferences: (action_name) =>
|
savePreferences: (action_name) =>
|
||||||
$http
|
$http
|
||||||
method: "PUT"
|
method: "PUT"
|
||||||
url: "/admin/column_preferences/bulk_update.json"
|
url: "/admin/column_preferences/bulk_update"
|
||||||
data:
|
data:
|
||||||
action_name: action_name
|
action_name: action_name
|
||||||
column_preferences: (preference for column_name, preference of @columns)
|
column_preferences: (preference for column_name, preference of @columns)
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, Sta
|
|||||||
remove: (id, attr) =>
|
remove: (id, attr) =>
|
||||||
if @pendingChanges.hasOwnProperty("#{id}")
|
if @pendingChanges.hasOwnProperty("#{id}")
|
||||||
delete @pendingChanges["#{id}"]["#{attr}"]
|
delete @pendingChanges["#{id}"]["#{attr}"]
|
||||||
|
delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1
|
||||||
if @changeCount( @pendingChanges["#{id}"] ) < 1
|
|
||||||
delete @pendingChanges["#{id}"]
|
|
||||||
StatusMessage.clear()
|
|
||||||
|
|
||||||
submitAll: (form=null) =>
|
submitAll: (form=null) =>
|
||||||
all = []
|
all = []
|
||||||
@@ -50,8 +47,5 @@ angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, Sta
|
|||||||
unsavedCount: ->
|
unsavedCount: ->
|
||||||
Object.keys(@pendingChanges).length
|
Object.keys(@pendingChanges).length
|
||||||
|
|
||||||
yes: ->
|
|
||||||
@unsavedCount() > 0
|
|
||||||
|
|
||||||
changeCount: (objectChanges) ->
|
changeCount: (objectChanges) ->
|
||||||
Object.keys(objectChanges).length
|
Object.keys(objectChanges).length
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
angular.module("admin.indexUtils").factory "switchClass", ($timeout, StatusMessage) ->
|
angular.module("admin.indexUtils").factory "switchClass", ($timeout) ->
|
||||||
return (element, classToAdd, removeClasses, timeout) ->
|
return (element, classToAdd, removeClasses, timeout) ->
|
||||||
$timeout.cancel element.timeout if element.timeout
|
$timeout.cancel element.timeout if element.timeout
|
||||||
element.removeClass className for className in removeClasses
|
element.removeClass className for className in removeClasses
|
||||||
@@ -7,6 +7,4 @@ angular.module("admin.indexUtils").factory "switchClass", ($timeout, StatusMessa
|
|||||||
if timeout && intRegex.test(timeout)
|
if timeout && intRegex.test(timeout)
|
||||||
element.timeout = $timeout(->
|
element.timeout = $timeout(->
|
||||||
element.removeClass classToAdd
|
element.removeClass classToAdd
|
||||||
StatusMessage.clear()
|
|
||||||
, timeout, true)
|
, timeout, true)
|
||||||
element
|
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
|||||||
$scope.page = 1
|
$scope.page = 1
|
||||||
$scope.per_page = $scope.per_page_options[0].id
|
$scope.per_page = $scope.per_page_options[0].id
|
||||||
$scope.filterByVariantId = null
|
$scope.filterByVariantId = null
|
||||||
|
searchThrough = ["order_distributor_name",
|
||||||
|
"order_bill_address_phone",
|
||||||
|
"order_bill_address_firstname",
|
||||||
|
"order_bill_address_lastname",
|
||||||
|
"order_bill_address_full_name",
|
||||||
|
"variant_product_supplier_name",
|
||||||
|
"order_email",
|
||||||
|
"order_number",
|
||||||
|
"product_name"].join("_or_") + "_cont"
|
||||||
|
|
||||||
$scope.confirmRefresh = ->
|
$scope.confirmRefresh = ->
|
||||||
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
|
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
|
||||||
@@ -63,19 +72,19 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
|||||||
[formattedStartDate, formattedEndDate] = $scope.formatDates($scope.startDate, $scope.endDate)
|
[formattedStartDate, formattedEndDate] = $scope.formatDates($scope.startDate, $scope.endDate)
|
||||||
|
|
||||||
RequestMonitor.load LineItems.index(
|
RequestMonitor.load LineItems.index(
|
||||||
|
"q[#{searchThrough}]": $scope.query,
|
||||||
|
"q[variant_id_eq]": $scope.filterByVariantId if $scope.filterByVariantId,
|
||||||
"q[order_state_not_eq]": "canceled",
|
"q[order_state_not_eq]": "canceled",
|
||||||
"q[order_shipment_state_not_eq]": "shipped",
|
"q[order_shipment_state_not_eq]": "shipped",
|
||||||
"q[order_completed_at_not_null]": "true",
|
"q[order_completed_at_not_null]": "true",
|
||||||
"q[variant_id_eq]": $scope.filterByVariantId if $scope.filterByVariantId,
|
|
||||||
"q[order_distributor_id_eq]": $scope.distributorFilter,
|
"q[order_distributor_id_eq]": $scope.distributorFilter,
|
||||||
"q[variant_supplier_id_eq]": $scope.supplierFilter,
|
"q[variant_product_supplier_id_eq]": $scope.supplierFilter,
|
||||||
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
|
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
|
||||||
"q[order_completed_at_gteq]": if formattedStartDate then formattedStartDate else undefined,
|
"q[order_completed_at_gteq]": if formattedStartDate then formattedStartDate else undefined,
|
||||||
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
|
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
|
||||||
"q[s]": "order_completed_at desc",
|
"q[s]": "order_completed_at desc",
|
||||||
"page": $scope.page,
|
"page": $scope.page,
|
||||||
"per_page": $scope.per_page,
|
"per_page": $scope.per_page
|
||||||
"search_query": $scope.query
|
|
||||||
)
|
)
|
||||||
|
|
||||||
$scope.formatDates = (startDate, endDate) ->
|
$scope.formatDates = (startDate, endDate) ->
|
||||||
@@ -85,7 +94,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
|||||||
|
|
||||||
$scope.loadAssociatedData = ->
|
$scope.loadAssociatedData = ->
|
||||||
RequestMonitor.load $scope.distributors = Enterprises.index(action: "visible", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
|
RequestMonitor.load $scope.distributors = Enterprises.index(action: "visible", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
|
||||||
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{moment().subtract(1,'year').format()}")
|
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{moment().subtract(90,'days').format()}")
|
||||||
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
|
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
|
||||||
|
|
||||||
$scope.dereferenceLoadedData = ->
|
$scope.dereferenceLoadedData = ->
|
||||||
@@ -187,14 +196,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
|||||||
$scope.refreshData()
|
$scope.refreshData()
|
||||||
|
|
||||||
$scope.getLineItemScale = (lineItem) ->
|
$scope.getLineItemScale = (lineItem) ->
|
||||||
if lineItem.units_variant && lineItem.units_variant.variant_unit_scale && (lineItem.units_variant.variant_unit == "weight" || lineItem.units_variant.variant_unit == "volume")
|
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
|
||||||
lineItem.units_variant.variant_unit_scale
|
lineItem.units_product.variant_unit_scale
|
||||||
else
|
else
|
||||||
1
|
1
|
||||||
|
|
||||||
$scope.sumUnitValues = ->
|
$scope.sumUnitValues = ->
|
||||||
sum = $scope.filteredLineItems?.reduce (sum, lineItem) ->
|
sum = $scope.filteredLineItems?.reduce (sum, lineItem) ->
|
||||||
if lineItem.units_variant.variant_unit == "items"
|
if lineItem.units_product.variant_unit == "items"
|
||||||
sum + lineItem.quantity
|
sum + lineItem.quantity
|
||||||
else
|
else
|
||||||
sum + $scope.roundToThreeDecimals(lineItem.final_weight_volume / $scope.getLineItemScale(lineItem))
|
sum + $scope.roundToThreeDecimals(lineItem.final_weight_volume / $scope.getLineItemScale(lineItem))
|
||||||
@@ -202,7 +211,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
|||||||
|
|
||||||
$scope.sumMaxUnitValues = ->
|
$scope.sumMaxUnitValues = ->
|
||||||
sum = $scope.filteredLineItems?.reduce (sum,lineItem) ->
|
sum = $scope.filteredLineItems?.reduce (sum,lineItem) ->
|
||||||
if lineItem.units_variant.variant_unit == "items"
|
if lineItem.units_product.variant_unit == "items"
|
||||||
sum + lineItem.max_quantity
|
sum + lineItem.max_quantity
|
||||||
else
|
else
|
||||||
sum + lineItem.max_quantity * $scope.roundToThreeDecimals(lineItem.units_variant.unit_value / $scope.getLineItemScale(lineItem))
|
sum + lineItem.max_quantity * $scope.roundToThreeDecimals(lineItem.units_variant.unit_value / $scope.getLineItemScale(lineItem))
|
||||||
@@ -216,41 +225,39 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
|||||||
return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0)
|
return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0)
|
||||||
true
|
true
|
||||||
|
|
||||||
$scope.getScale = (unitsVariant) ->
|
$scope.getScale = (unitsProduct, unitsVariant) ->
|
||||||
if unitsVariant.hasOwnProperty("variant_unit") && (unitsVariant.variant_unit == "weight" || unitsVariant.variant_unit == "volume")
|
if unitsProduct.hasOwnProperty("variant_unit") && (unitsProduct.variant_unit == "weight" || unitsProduct.variant_unit == "volume")
|
||||||
unitsVariant.variant_unit_scale
|
unitsProduct.variant_unit_scale
|
||||||
else if unitsVariant.hasOwnProperty("variant_unit") && unitsVariant.variant_unit == "items"
|
else if unitsProduct.hasOwnProperty("variant_unit") && unitsProduct.variant_unit == "items"
|
||||||
1
|
1
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
|
|
||||||
$scope.getFormattedValueWithUnitName = (value, unitsVariant, scale) ->
|
$scope.getFormattedValueWithUnitName = (value, unitsProduct, unitsVariant, scale) ->
|
||||||
unit_name = VariantUnitManager.getUnitName(scale, unitsVariant.variant_unit)
|
unit_name = VariantUnitManager.getUnitName(scale, unitsProduct.variant_unit)
|
||||||
$scope.roundToThreeDecimals(value) + " " + unit_name
|
$scope.roundToThreeDecimals(value) + " " + unit_name
|
||||||
|
|
||||||
$scope.getGroupBySizeFormattedValueWithUnitName = (value, unitsVariant) ->
|
$scope.getGroupBySizeFormattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
|
||||||
scale = $scope.getScale(unitsVariant)
|
scale = $scope.getScale(unitsProduct, unitsVariant)
|
||||||
if scale && value
|
if scale && value
|
||||||
value = value / scale if scale != 28.35 && scale != 1 && scale != 453.6 # divide by scale if not smallest unit
|
value = value / scale if scale != 28.35 && scale != 1 && scale != 453.6 # divide by scale if not smallest unit
|
||||||
$scope.getFormattedValueWithUnitName(value, unitsVariant, scale)
|
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
|
||||||
else
|
else
|
||||||
''
|
''
|
||||||
|
|
||||||
$scope.formattedValueWithUnitName = (value, unitsVariant) ->
|
$scope.formattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
|
||||||
scale = $scope.getScale(unitsVariant)
|
scale = $scope.getScale(unitsProduct, unitsVariant)
|
||||||
if scale
|
if scale
|
||||||
$scope.getFormattedValueWithUnitName(value, unitsVariant, scale)
|
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
|
||||||
else
|
else
|
||||||
''
|
''
|
||||||
|
|
||||||
$scope.fulfilled = (sumOfUnitValues) ->
|
$scope.fulfilled = (sumOfUnitValues) ->
|
||||||
# A Units Variant is an API object which holds unit properies of a variant
|
# A Units Variant is an API object which holds unit properies of a variant
|
||||||
if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size") && $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
|
if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size")&& $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
|
||||||
$scope.selectedUnitsVariant.hasOwnProperty("variant_unit")
|
$scope.selectedUnitsProduct.hasOwnProperty("variant_unit")
|
||||||
|
if $scope.selectedUnitsProduct.variant_unit == "weight" || $scope.selectedUnitsProduct.variant_unit == "volume"
|
||||||
if $scope.selectedUnitsVariant.variant_unit == "weight" || $scope.selectedUnitsVariant.variant_unit == "volume"
|
scale = $scope.selectedUnitsProduct.variant_unit_scale
|
||||||
|
|
||||||
scale = $scope.selectedUnitsVariant.variant_unit_scale
|
|
||||||
sumOfUnitValues = sumOfUnitValues * scale unless scale == 28.35 || scale == 453.6
|
sumOfUnitValues = sumOfUnitValues * scale unless scale == 28.35 || scale == 453.6
|
||||||
$scope.roundToThreeDecimals(sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size)
|
$scope.roundToThreeDecimals(sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ angular.module('admin.orderCycles')
|
|||||||
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
||||||
|
|
||||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||||
$scope.order_cycle_id = order_cycle_id
|
|
||||||
$scope.order_cycle = OrderCycle.load(order_cycle_id)
|
$scope.order_cycle = OrderCycle.load(order_cycle_id)
|
||||||
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
|
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
|
||||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
|
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
|
||||||
@@ -19,8 +18,6 @@ angular.module('admin.orderCycles')
|
|||||||
|
|
||||||
$scope.submit = ($event, destination) ->
|
$scope.submit = ($event, destination) ->
|
||||||
$event.preventDefault()
|
$event.preventDefault()
|
||||||
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
|
|
||||||
$scope.order_cycle?.confirm = $($event.target).data('confirm');
|
|
||||||
StatusMessage.display 'progress', t('js.saving')
|
StatusMessage.display 'progress', t('js.saving')
|
||||||
OrderCycle.update(destination, $scope.order_cycle_form)
|
OrderCycle.update(destination, $scope.order_cycle_form)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, EnterpriseFee, OrderCycle, ExchangeProduct, ocInstance) ->
|
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||||
$controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
|
$controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
|
||||||
|
|
||||||
$scope.view = 'incoming'
|
$scope.view = 'incoming'
|
||||||
# NB: weirdly at this next line $scope.order_cycle.id comes out undefined so we use $scope.order_cycle_id instead
|
|
||||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: $scope.order_cycle_id, per_item: true)
|
|
||||||
$scope.exchangeTotalVariants = (exchange) ->
|
$scope.exchangeTotalVariants = (exchange) ->
|
||||||
return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?
|
return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, Columns, StatusMessage, RequestMonitor, OrderCycles, Enterprises, Schedules, Dereferencer) ->
|
angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, Columns, StatusMessage, RequestMonitor, OrderCycles, Enterprises, Schedules, Dereferencer) ->
|
||||||
$scope.RequestMonitor = RequestMonitor
|
$scope.RequestMonitor = RequestMonitor
|
||||||
$scope.columns = Columns.columns
|
$scope.columns = Columns.columns
|
||||||
$scope.saveAll = ($event) ->
|
$scope.saveAll = -> OrderCycles.saveChanges($scope.order_cycles_form)
|
||||||
trigger_action = $($event.target).data('trigger-action')
|
|
||||||
confirm = $($event.target).data('confirm')
|
|
||||||
OrderCycles.saveChanges($scope.order_cycles_form, { trigger_action, confirm })
|
|
||||||
|
|
||||||
$scope.ordersCloseAtLimit = -31 # days
|
$scope.ordersCloseAtLimit = -31 # days
|
||||||
|
|
||||||
$scope.resetSelectFilters = ->
|
$scope.resetSelectFilters = ->
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
|
|||||||
|
|
||||||
$scope.submit = ($event, destination) ->
|
$scope.submit = ($event, destination) ->
|
||||||
$event.preventDefault()
|
$event.preventDefault()
|
||||||
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
|
|
||||||
$scope.order_cycle?.confirm = $($event.target).data('confirm');
|
|
||||||
StatusMessage.display 'progress', t('js.saving')
|
StatusMessage.display 'progress', t('js.saving')
|
||||||
OrderCycle.mirrorIncomingToOutgoingProducts()
|
OrderCycle.mirrorIncomingToOutgoingProducts()
|
||||||
OrderCycle.update(destination, $scope.order_cycle_form) if OrderCycle.confirmNoDistributors()
|
OrderCycle.update(destination, $scope.order_cycle_form) if OrderCycle.confirmNoDistributors()
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ angular.module('admin.orderCycles').factory('EnterpriseFee', ($resource) ->
|
|||||||
params:
|
params:
|
||||||
order_cycle_id: '@order_cycle_id'
|
order_cycle_id: '@order_cycle_id'
|
||||||
coordinator_id: '@coordinator_id'
|
coordinator_id: '@coordinator_id'
|
||||||
per_item: '@per_item'
|
|
||||||
per_order: '@per_order'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -161,11 +161,7 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
|
|||||||
StatusMessage.display('failure', t('js.order_cycles.create_failure'))
|
StatusMessage.display('failure', t('js.order_cycles.create_failure'))
|
||||||
|
|
||||||
update: (destination, form) ->
|
update: (destination, form) ->
|
||||||
oc = new OrderCycleResource({
|
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
|
||||||
order_cycle: this.dataForSubmit(),
|
|
||||||
confirm: this.order_cycle.confirm,
|
|
||||||
trigger_action: this.order_cycle.trigger_action
|
|
||||||
})
|
|
||||||
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
|
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
|
||||||
form.$setPristine() if form
|
form.$setPristine() if form
|
||||||
if destination?
|
if destination?
|
||||||
@@ -175,8 +171,6 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
|
|||||||
, (response) ->
|
, (response) ->
|
||||||
if response.data.errors?
|
if response.data.errors?
|
||||||
StatusMessage.display('failure', response.data.errors[0])
|
StatusMessage.display('failure', response.data.errors[0])
|
||||||
else if (response.data.trigger_action)
|
|
||||||
StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action)
|
|
||||||
else
|
else
|
||||||
StatusMessage.display('failure', t('js.order_cycles.update_failure'))
|
StatusMessage.display('failure', t('js.order_cycles.update_failure'))
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ angular.module("admin.orders").controller "orderCtrl", ($scope, shops, orderCycl
|
|||||||
|
|
||||||
$scope.distributor_id = parseInt($attrs.ofnDistributorId)
|
$scope.distributor_id = parseInt($attrs.ofnDistributorId)
|
||||||
$scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId)
|
$scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId)
|
||||||
$scope.search_variants_as = $attrs.ofnSearchVariantsAs
|
|
||||||
$scope.order_id = $attrs.ofnOrderId
|
|
||||||
|
|
||||||
$scope.validOrderCycle = (oc) ->
|
$scope.validOrderCycle = (oc) ->
|
||||||
$scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id)
|
$scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id)
|
||||||
|
|||||||
@@ -15,4 +15,4 @@ angular.module("admin.paymentMethods").controller "StripeController", ($scope, $
|
|||||||
permalink = shops.filter((shop) ->
|
permalink = shops.filter((shop) ->
|
||||||
shop.id == $scope.paymentMethod.preferred_enterprise_id
|
shop.id == $scope.paymentMethod.preferred_enterprise_id
|
||||||
)[0].permalink
|
)[0].permalink
|
||||||
"/admin/enterprises/#{permalink}/edit#/payment_methods_panel"
|
"/admin/enterprises/#{permalink}/edit#/payment_methods"
|
||||||
|
|||||||
@@ -42,10 +42,8 @@ angular.module('admin.payments').factory 'Payment', (AdminStripeElements, curren
|
|||||||
|
|
||||||
submit: =>
|
submit: =>
|
||||||
munged = @preprocess()
|
munged = @preprocess()
|
||||||
PaymentResource.create({order_id: munged.order_id}, munged, (response, headers, status) ->
|
PaymentResource.create({order_id: munged.order_id}, munged, (response, headers, status)=>
|
||||||
rawHtml = Object.values(response).join('').replace('[object Object]true', '')
|
$window.location.pathname = "/admin/orders/" + munged.order_id + "/payments"
|
||||||
document.body.innerHTML = rawHtml
|
|
||||||
$window.history.pushState({}, '', "/admin/orders/" + munged.order_id + "/payments")
|
|
||||||
, (response) ->
|
, (response) ->
|
||||||
StatusMessage.display 'error', t("spree.admin.payments.source_forms.stripe.error_saving_payment")
|
StatusMessage.display 'error', t("spree.admin.payments.source_forms.stripe.error_saving_payment")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
angular.module("admin.products").controller "editUnitsCtrl", ($scope, VariantUnitManager) ->
|
||||||
|
|
||||||
|
$scope.product =
|
||||||
|
variant_unit: angular.element('#variant_unit').val()
|
||||||
|
variant_unit_scale: angular.element('#variant_unit_scale').val()
|
||||||
|
|
||||||
|
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||||
|
|
||||||
|
if $scope.product.variant_unit == 'items'
|
||||||
|
$scope.variant_unit_with_scale = 'items'
|
||||||
|
else
|
||||||
|
$scope.variant_unit_with_scale = $scope.product.variant_unit + '_' + $scope.product.variant_unit_scale.replace(/\.0$/, '');
|
||||||
|
|
||||||
|
$scope.setFields = ->
|
||||||
|
if $scope.variant_unit_with_scale == 'items'
|
||||||
|
variant_unit = 'items'
|
||||||
|
variant_unit_scale = null
|
||||||
|
else
|
||||||
|
options = $scope.variant_unit_with_scale.split('_')
|
||||||
|
variant_unit = options[0]
|
||||||
|
variant_unit_scale = options[1]
|
||||||
|
|
||||||
|
$scope.product.variant_unit = variant_unit
|
||||||
|
$scope.product.variant_unit_scale = variant_unit_scale
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
angular.module("ofn.admin").controller "ProductImageCtrl", ($scope, ProductImageService) ->
|
||||||
|
$scope.imageUploader = ProductImageService.imageUploader
|
||||||
|
$scope.imagePreview = ProductImageService.imagePreview
|
||||||
|
|
||||||
|
$scope.$watch 'product.image_url', (newValue, oldValue) ->
|
||||||
|
if newValue != oldValue
|
||||||
|
$scope.imagePreview = newValue
|
||||||
|
$scope.uploadModal.close()
|
||||||
@@ -1,26 +1,24 @@
|
|||||||
# Controller for "New Products" form (spree/admin/products/new)
|
|
||||||
angular.module("admin.products")
|
angular.module("admin.products")
|
||||||
.controller "unitsCtrl", ($scope, VariantUnitManager, OptionValueNamer, UnitPrices, PriceParser) ->
|
.controller "unitsCtrl", ($scope, VariantUnitManager, OptionValueNamer, UnitPrices, PriceParser) ->
|
||||||
$scope.product = {}
|
$scope.product = { master: {} }
|
||||||
|
$scope.product.master.product = $scope.product
|
||||||
$scope.placeholder_text = ""
|
$scope.placeholder_text = ""
|
||||||
|
|
||||||
$scope.$watchCollection '[product.variant_unit_with_scale, product.unit_value_with_description, product.price, product.variant_unit_name]', ->
|
$scope.$watchCollection '[product.variant_unit_with_scale, product.master.unit_value_with_description, product.price, product.variant_unit_name]', ->
|
||||||
$scope.processVariantUnitWithScale()
|
$scope.processVariantUnitWithScale()
|
||||||
$scope.processUnitValueWithDescription()
|
$scope.processUnitValueWithDescription()
|
||||||
$scope.processUnitPrice()
|
$scope.processUnitPrice()
|
||||||
$scope.placeholder_text = new OptionValueNamer($scope.product).name() if $scope.product.variant_unit_scale
|
$scope.placeholder_text = new OptionValueNamer($scope.product.master).name() if $scope.product.variant_unit_scale
|
||||||
|
|
||||||
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||||
|
|
||||||
# Extract variant_unit and variant_unit_scale from dropdown variant_unit_with_scale,
|
|
||||||
# and update hidden product fields
|
|
||||||
$scope.processVariantUnitWithScale = ->
|
$scope.processVariantUnitWithScale = ->
|
||||||
if $scope.product.variant_unit_with_scale
|
if $scope.product.variant_unit_with_scale
|
||||||
match = $scope.product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/) # matches string like "weight_1000"
|
match = $scope.product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
|
||||||
if match
|
if match
|
||||||
$scope.product.variant_unit = match[1]
|
$scope.product.variant_unit = match[1]
|
||||||
$scope.product.variant_unit_scale = parseFloat(match[2])
|
$scope.product.variant_unit_scale = parseFloat(match[2])
|
||||||
else # "items"
|
else
|
||||||
$scope.product.variant_unit = $scope.product.variant_unit_with_scale
|
$scope.product.variant_unit = $scope.product.variant_unit_with_scale
|
||||||
$scope.product.variant_unit_scale = null
|
$scope.product.variant_unit_scale = null
|
||||||
else if $scope.product.variant_unit
|
else if $scope.product.variant_unit
|
||||||
@@ -34,27 +32,24 @@ angular.module("admin.products")
|
|||||||
else
|
else
|
||||||
$scope.product.variant_unit = $scope.product.variant_unit_scale = null
|
$scope.product.variant_unit = $scope.product.variant_unit_scale = null
|
||||||
|
|
||||||
# Extract unit_value and unit_description from text field unit_value_with_description,
|
|
||||||
# and update hidden variant fields
|
|
||||||
$scope.processUnitValueWithDescription = ->
|
$scope.processUnitValueWithDescription = ->
|
||||||
if $scope.product.hasOwnProperty("unit_value_with_description")
|
if $scope.product.master.hasOwnProperty("unit_value_with_description")
|
||||||
match = $scope.product.unit_value_with_description.match(/^([\d\.,]+(?= *|$)|)( *)(.*)$/)
|
match = $scope.product.master.unit_value_with_description.match(/^([\d\.,]+(?= *|$)|)( *)(.*)$/)
|
||||||
if match
|
if match
|
||||||
$scope.product.unit_value = PriceParser.parse(match[1])
|
$scope.product.master.unit_value = PriceParser.parse(match[1])
|
||||||
$scope.product.unit_value = null if isNaN($scope.product.unit_value)
|
$scope.product.master.unit_value = null if isNaN($scope.product.master.unit_value)
|
||||||
$scope.product.unit_value = window.bigDecimal.multiply($scope.product.unit_value, $scope.product.variant_unit_scale, 2) if $scope.product.unit_value && $scope.product.variant_unit_scale
|
$scope.product.master.unit_value = window.bigDecimal.multiply($scope.product.master.unit_value, $scope.product.variant_unit_scale, 2) if $scope.product.master.unit_value && $scope.product.variant_unit_scale
|
||||||
$scope.product.unit_description = match[3]
|
$scope.product.master.unit_description = match[3]
|
||||||
else
|
else
|
||||||
value = $scope.product.unit_value
|
value = $scope.product.master.unit_value
|
||||||
value = window.bigDecimal.divide(value, $scope.product.variant_unit_scale, 2) if $scope.product.unit_value && $scope.product.variant_unit_scale
|
value = window.bigDecimal.divide(value, $scope.product.variant_unit_scale, 2) if $scope.product.master.unit_value && $scope.product.variant_unit_scale
|
||||||
$scope.product.unit_value_with_description = value + " " + $scope.product.unit_description
|
$scope.product.master.unit_value_with_description = value + " " + $scope.product.master.unit_description
|
||||||
|
|
||||||
# Calculate unit price based on product price and variant_unit_scale
|
|
||||||
$scope.processUnitPrice = ->
|
$scope.processUnitPrice = ->
|
||||||
price = $scope.product.price
|
price = $scope.product.price
|
||||||
scale = $scope.product.variant_unit_scale
|
scale = $scope.product.variant_unit_scale
|
||||||
unit_type = $scope.product.variant_unit
|
unit_type = $scope.product.variant_unit
|
||||||
unit_value = $scope.product.unit_value
|
unit_value = $scope.product.master.unit_value
|
||||||
variant_unit_name = $scope.product.variant_unit_name
|
variant_unit_name = $scope.product.variant_unit_name
|
||||||
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)
|
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
angular.module("admin.products").controller "variantUnitsCtrl", ($scope, VariantUnitManager, $timeout, UnitPrices, PriceParser) ->
|
||||||
|
|
||||||
|
$scope.unitName = (scale, type) ->
|
||||||
|
VariantUnitManager.getUnitName(scale, type)
|
||||||
|
|
||||||
|
$scope.$watchCollection "[unit_value_human, variant.price]", ->
|
||||||
|
$scope.processUnitPrice()
|
||||||
|
|
||||||
|
$scope.processUnitPrice = ->
|
||||||
|
if ($scope.variant)
|
||||||
|
price = $scope.variant.price
|
||||||
|
scale = $scope.scale
|
||||||
|
unit_type = angular.element("#product_variant_unit").val()
|
||||||
|
if (unit_type != "items")
|
||||||
|
$scope.updateValue()
|
||||||
|
unit_value = $scope.unit_value
|
||||||
|
else
|
||||||
|
unit_value = 1
|
||||||
|
variant_unit_name = angular.element("#product_variant_unit_name").val()
|
||||||
|
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)
|
||||||
|
|
||||||
|
$scope.scale = angular.element('#product_variant_unit_scale').val()
|
||||||
|
|
||||||
|
$scope.updateValue = ->
|
||||||
|
unit_value_human = angular.element('#unit_value_human').val()
|
||||||
|
$scope.unit_value = bigDecimal.multiply(PriceParser.parse(unit_value_human), $scope.scale, 2)
|
||||||
|
|
||||||
|
variant_unit_value = angular.element('#variant_unit_value').val()
|
||||||
|
$scope.unit_value_human = parseFloat(bigDecimal.divide(variant_unit_value, $scope.scale, 2))
|
||||||
|
|
||||||
|
$timeout -> $scope.processUnitPrice()
|
||||||
|
$timeout -> $scope.updateValue()
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
angular.module("ofn.admin").directive "imageModal", ($modal, ProductImageService) ->
|
||||||
|
restrict: 'C'
|
||||||
|
link: (scope, elem, attrs, ctrl) ->
|
||||||
|
elem.on "click", (ev) =>
|
||||||
|
scope.uploadModal = $modal.open(templateUrl: 'admin/modals/image_upload.html', controller: ctrl, scope: scope, windowClass: 'simple-modal')
|
||||||
|
ProductImageService.configure(scope.product)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
angular.module("admin.products").directive "setOnDemand", ->
|
||||||
|
link: (scope, element, attr) ->
|
||||||
|
onHand = element.context.querySelector("#variant_on_hand")
|
||||||
|
onDemand = element.context.querySelector("#variant_on_demand")
|
||||||
|
|
||||||
|
disableOnHandIfOnDemand = ->
|
||||||
|
if onDemand.checked
|
||||||
|
onHand.disabled = 'disabled'
|
||||||
|
onHand.dataStock = onHand.value
|
||||||
|
onHand.value = t('admin.products.variants.infinity')
|
||||||
|
|
||||||
|
disableOnHandIfOnDemand()
|
||||||
|
|
||||||
|
onDemand.addEventListener 'change', (event) ->
|
||||||
|
disableOnHandIfOnDemand()
|
||||||
|
|
||||||
|
if !onDemand.checked
|
||||||
|
onHand.removeAttribute('disabled')
|
||||||
|
onHand.value = onHand.dataStock
|
||||||
@@ -1 +1 @@
|
|||||||
angular.module("admin.products", ["ngSanitize", "admin.utils", "OFNShared"])
|
angular.module("admin.products", ["textAngular", "admin.utils", "OFNShared"])
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager) ->
|
angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager) ->
|
||||||
# Javascript clone of VariantUnits::OptionValueNamer, for bulk product editing.
|
|
||||||
class OptionValueNamer
|
class OptionValueNamer
|
||||||
constructor: (@variant) ->
|
constructor: (@variant) ->
|
||||||
|
|
||||||
@@ -13,16 +12,16 @@ angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager
|
|||||||
name_fields.join ' '
|
name_fields.join ' '
|
||||||
|
|
||||||
value_scaled: ->
|
value_scaled: ->
|
||||||
@variant.variant_unit_scale?
|
@variant.product.variant_unit_scale?
|
||||||
|
|
||||||
option_value_value_unit: ->
|
option_value_value_unit: ->
|
||||||
if @variant.unit_value?
|
if @variant.unit_value?
|
||||||
if @variant.variant_unit in ["weight", "volume"]
|
if @variant.product.variant_unit in ["weight", "volume"]
|
||||||
[value, unit_name] = @option_value_value_unit_scaled()
|
[value, unit_name] = @option_value_value_unit_scaled()
|
||||||
|
|
||||||
else
|
else
|
||||||
value = @variant.unit_value
|
value = @variant.unit_value
|
||||||
unit_name = @pluralize(@variant.variant_unit_name, value)
|
unit_name = @pluralize(@variant.product.variant_unit_name, value)
|
||||||
|
|
||||||
value = parseInt(value, 10) if value == parseInt(value, 10)
|
value = parseInt(value, 10) if value == parseInt(value, 10)
|
||||||
|
|
||||||
@@ -58,13 +57,14 @@ angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager
|
|||||||
# to >= 1 when expressed in it.
|
# to >= 1 when expressed in it.
|
||||||
# If there is none available where this is true, use the smallest
|
# If there is none available where this is true, use the smallest
|
||||||
# available unit.
|
# available unit.
|
||||||
scales = VariantUnitManager.compatibleUnitScales(@variant.variant_unit_scale, @variant.variant_unit)
|
product = @variant.product
|
||||||
|
scales = VariantUnitManager.compatibleUnitScales(product.variant_unit_scale, product.variant_unit)
|
||||||
variantUnitValue = @variant.unit_value
|
variantUnitValue = @variant.unit_value
|
||||||
|
|
||||||
# sets largestScale = last element in filtered scales array
|
# sets largestScale = last element in filtered scales array
|
||||||
[_, ..., largestScale] = (scales.filter (s) -> variantUnitValue / s >= 1)
|
[_, ..., largestScale] = (scales.filter (s) -> variantUnitValue / s >= 1)
|
||||||
|
|
||||||
if (largestScale)
|
if (largestScale)
|
||||||
[largestScale, VariantUnitManager.getUnitName(largestScale, @variant.variant_unit)]
|
[largestScale, VariantUnitManager.getUnitName(largestScale, product.variant_unit)]
|
||||||
else
|
else
|
||||||
[scales[0], VariantUnitManager.getUnitName(scales[0], @variant.variant_unit)]
|
[scales[0], VariantUnitManager.getUnitName(scales[0], product.variant_unit)]
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ angular.module("admin.products").factory "VariantUnitManager", (availableUnits)
|
|||||||
class VariantUnitManager
|
class VariantUnitManager
|
||||||
@units:
|
@units:
|
||||||
'weight':
|
'weight':
|
||||||
0.001:
|
|
||||||
name: 'mg'
|
|
||||||
system: 'metric'
|
|
||||||
1.0:
|
1.0:
|
||||||
name: 'g'
|
name: 'g'
|
||||||
system: 'metric'
|
system: 'metric'
|
||||||
@@ -24,21 +21,12 @@ angular.module("admin.products").factory "VariantUnitManager", (availableUnits)
|
|||||||
0.001:
|
0.001:
|
||||||
name: 'mL'
|
name: 'mL'
|
||||||
system: 'metric'
|
system: 'metric'
|
||||||
0.01:
|
|
||||||
name: 'cL'
|
|
||||||
system: 'metric'
|
|
||||||
0.1:
|
|
||||||
name: 'dL'
|
|
||||||
system: 'metric'
|
|
||||||
1.0:
|
1.0:
|
||||||
name: 'L'
|
name: 'L'
|
||||||
system: 'metric'
|
system: 'metric'
|
||||||
1000.0:
|
1000.0:
|
||||||
name: 'kL'
|
name: 'kL'
|
||||||
system: 'metric'
|
system: 'metric'
|
||||||
4.54609:
|
|
||||||
name: 'gal'
|
|
||||||
system: 'metric'
|
|
||||||
'items':
|
'items':
|
||||||
1:
|
1:
|
||||||
name: 'items'
|
name: 'items'
|
||||||
@@ -72,13 +60,8 @@ angular.module("admin.products").factory "VariantUnitManager", (availableUnits)
|
|||||||
|
|
||||||
@compatibleUnitScales: (scale, unitType) ->
|
@compatibleUnitScales: (scale, unitType) ->
|
||||||
scaleSystem = @units[unitType][scale]['system']
|
scaleSystem = @units[unitType][scale]['system']
|
||||||
if availableUnits
|
(parseFloat(scale) for scale, scaleInfo of @units[unitType] when scaleInfo['system'] == scaleSystem).sort (a, b) ->
|
||||||
available = availableUnits.split(",")
|
a - b
|
||||||
(parseFloat(scale) for scale, scaleInfo of @units[unitType] when scaleInfo['system'] == scaleSystem and available.includes(scaleInfo['name'])).sort (a, b) ->
|
|
||||||
a - b
|
|
||||||
else
|
|
||||||
(parseFloat(scale) for scale, scaleInfo of @units[unitType] when scaleInfo['system'] == scaleSystem).sort (a, b) ->
|
|
||||||
a - b
|
|
||||||
|
|
||||||
@systemOfMeasurement: (scale, unitType) ->
|
@systemOfMeasurement: (scale, unitType) ->
|
||||||
if @units[unitType][scale]
|
if @units[unitType][scale]
|
||||||
|
|||||||
@@ -8,4 +8,10 @@ angular.module("admin.resources").factory 'EnterpriseResource', ($resource) ->
|
|||||||
isArray: true
|
isArray: true
|
||||||
'update':
|
'update':
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
|
'removeLogo':
|
||||||
|
url: '/api/v0/enterprises/:id/logo.json'
|
||||||
|
method: 'DELETE'
|
||||||
|
'removePromoImage':
|
||||||
|
url: '/api/v0/enterprises/:id/promo_image.json'
|
||||||
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
angular.module("admin.resources").factory 'ProductResource', ($resource) ->
|
||||||
|
$resource('/admin/product/:id/:action.json', {}, {
|
||||||
|
'index':
|
||||||
|
url: '/api/v0/products/bulk_products.json'
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
@@ -39,6 +39,17 @@ angular.module("admin.resources").factory 'Enterprises', ($q, $filter, Enterpris
|
|||||||
resetAttribute: (enterprise, attribute) ->
|
resetAttribute: (enterprise, attribute) ->
|
||||||
enterprise[attribute] = @pristineByID[enterprise.id][attribute]
|
enterprise[attribute] = @pristineByID[enterprise.id][attribute]
|
||||||
|
|
||||||
|
performActionOnEnterpriseResource = (resourceAction) ->
|
||||||
|
(enterprise) ->
|
||||||
|
deferred = $q.defer()
|
||||||
|
resourceAction({id: enterprise.permalink}, ((data) =>
|
||||||
|
@pristineByID[enterprise.id] = angular.copy(data)
|
||||||
|
deferred.resolve(data)
|
||||||
|
), ((response) ->
|
||||||
|
deferred.reject(response)
|
||||||
|
))
|
||||||
|
deferred.promise
|
||||||
|
|
||||||
findByID: (id) ->
|
findByID: (id) ->
|
||||||
@byID[id]
|
@byID[id]
|
||||||
|
|
||||||
@@ -50,3 +61,5 @@ angular.module("admin.resources").factory 'Enterprises', ($q, $filter, Enterpris
|
|||||||
$filter('filter')(enterprises, term)
|
$filter('filter')(enterprises, term)
|
||||||
|
|
||||||
|
|
||||||
|
removeLogo: performActionOnEnterpriseResource(EnterpriseResource.removeLogo)
|
||||||
|
removePromoImage: performActionOnEnterpriseResource(EnterpriseResource.removePromoImage)
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy
|
|||||||
deferred.reject(response)
|
deferred.reject(response)
|
||||||
deferred.promise
|
deferred.promise
|
||||||
|
|
||||||
saveChanges: (form, params = {}) ->
|
saveChanges: (form) ->
|
||||||
changed = {}
|
changed = {}
|
||||||
for id, orderCycle of @byID when not @saved(orderCycle)
|
for id, orderCycle of @byID when not @saved(orderCycle)
|
||||||
changed[Object.keys(changed).length] = @changesFor(orderCycle)
|
changed[Object.keys(changed).length] = @changesFor(orderCycle)
|
||||||
if Object.keys(changed).length > 0
|
if Object.keys(changed).length > 0
|
||||||
StatusMessage.display('progress', "Saving...")
|
StatusMessage.display('progress', "Saving...")
|
||||||
OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed }, confirm: params['confirm'], trigger_action: params['trigger_action'] }, (data) =>
|
OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed } }, (data) =>
|
||||||
for orderCycle in data
|
for orderCycle in data
|
||||||
delete orderCycle.coordinator
|
delete orderCycle.coordinator
|
||||||
delete orderCycle.producers
|
delete orderCycle.producers
|
||||||
@@ -47,10 +47,8 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy
|
|||||||
, (response) =>
|
, (response) =>
|
||||||
if response.data.errors?
|
if response.data.errors?
|
||||||
StatusMessage.display('failure', response.data.errors[0])
|
StatusMessage.display('failure', response.data.errors[0])
|
||||||
else if (response.data.trigger_action)
|
|
||||||
StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action)
|
|
||||||
else
|
else
|
||||||
StatusMessage.display('failure', t('js.order_cycles.bulk_save_error'))
|
StatusMessage.display('failure', "Oh no! I was unable to save your changes.")
|
||||||
|
|
||||||
saved: (order_cycle) ->
|
saved: (order_cycle) ->
|
||||||
@diff(order_cycle).length == 0
|
@diff(order_cycle).length == 0
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) ->
|
||||||
|
new class BulkProducts
|
||||||
|
products: []
|
||||||
|
pagination: {}
|
||||||
|
|
||||||
|
fetch: (params) ->
|
||||||
|
ProductResource.index params, (data) =>
|
||||||
|
@products.length = 0
|
||||||
|
@addProducts data.products
|
||||||
|
angular.extend(@pagination, data.pagination)
|
||||||
|
|
||||||
|
cloneProduct: (product) ->
|
||||||
|
$http.post("/api/v0/products/" + product.id + "/clone").then (response) =>
|
||||||
|
dataFetcher("/api/v0/products/" + response.data.id + "?template=bulk_show").then (newProduct) =>
|
||||||
|
@unpackProduct newProduct
|
||||||
|
@insertProductAfter(product, newProduct)
|
||||||
|
|
||||||
|
updateVariantLists: (serverProducts) ->
|
||||||
|
for server_product in serverProducts
|
||||||
|
product = @findProductInList(server_product.id, @products)
|
||||||
|
product.variants = server_product.variants
|
||||||
|
@loadVariantUnitValues product
|
||||||
|
|
||||||
|
find: (id) ->
|
||||||
|
@findProductInList id, @products
|
||||||
|
|
||||||
|
findProductInList: (id, product_list) ->
|
||||||
|
products = (product for product in product_list when product.id == id)
|
||||||
|
if products.length == 0 then null else products[0]
|
||||||
|
|
||||||
|
addProducts: (products) ->
|
||||||
|
for product in products
|
||||||
|
@unpackProduct product
|
||||||
|
@products.push product
|
||||||
|
|
||||||
|
insertProductAfter: (product, newProduct) ->
|
||||||
|
index = @products.indexOf(product)
|
||||||
|
@products.splice(index + 1, 0, newProduct)
|
||||||
|
|
||||||
|
unpackProduct: (product) ->
|
||||||
|
#$scope.matchProducer product
|
||||||
|
@loadVariantUnit product
|
||||||
|
|
||||||
|
loadVariantUnit: (product) ->
|
||||||
|
product.variant_unit_with_scale =
|
||||||
|
if product.variant_unit && product.variant_unit_scale && product.variant_unit != 'items'
|
||||||
|
"#{product.variant_unit}_#{product.variant_unit_scale}"
|
||||||
|
else if product.variant_unit
|
||||||
|
product.variant_unit
|
||||||
|
else
|
||||||
|
null
|
||||||
|
|
||||||
|
@loadVariantUnitValues product if product.variants
|
||||||
|
@loadVariantUnitValue product, product.master if product.master
|
||||||
|
|
||||||
|
loadVariantUnitValues: (product) ->
|
||||||
|
for variant in product.variants
|
||||||
|
@loadVariantUnitValue product, variant
|
||||||
|
|
||||||
|
loadVariantUnitValue: (product, variant) ->
|
||||||
|
unit_value = @variantUnitValue product, variant
|
||||||
|
unit_value = if unit_value? then unit_value else ''
|
||||||
|
variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim()
|
||||||
|
|
||||||
|
variantUnitValue: (product, variant) ->
|
||||||
|
if variant.unit_value?
|
||||||
|
if product.variant_unit_scale
|
||||||
|
variant_unit_value = @divideAsInteger variant.unit_value, product.variant_unit_scale
|
||||||
|
parseFloat(window.bigDecimal.round(variant_unit_value, 2))
|
||||||
|
else
|
||||||
|
variant.unit_value
|
||||||
|
else
|
||||||
|
null
|
||||||
|
|
||||||
|
# forces integer division to avoid javascript floating point imprecision
|
||||||
|
# using one billion as the multiplier so that it works for numbers with up to 9 decimal places
|
||||||
|
divideAsInteger: (a, b) ->
|
||||||
|
(a * 1000000000) / (b * 1000000000)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
angular.module("admin.indexUtils").factory 'KeyValueMapStore', (localStorageService)->
|
||||||
|
new class KeyValueMapStore
|
||||||
|
localStorageKey: ''
|
||||||
|
storableKeys: []
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
localStorageService.setStorageType("sessionStorage")
|
||||||
|
|
||||||
|
getStoredKeyValueMap: ->
|
||||||
|
localStorageService.get(@localStorageKey) || {}
|
||||||
|
|
||||||
|
setStoredValues: (source) ->
|
||||||
|
keyValueMap = {}
|
||||||
|
for key in @storableKeys
|
||||||
|
keyValueMap[key] = source[key]
|
||||||
|
localStorageService.set(@localStorageKey, keyValueMap)
|
||||||
|
|
||||||
|
restoreValues: (target) ->
|
||||||
|
storedKeyValueMap = @getStoredKeyValueMap()
|
||||||
|
|
||||||
|
return false if _.isEmpty(storedKeyValueMap)
|
||||||
|
|
||||||
|
for k,v of storedKeyValueMap
|
||||||
|
target[k] = v
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
clearKeyValueMap: () ->
|
||||||
|
localStorageService.remove(@localStorageKey)
|
||||||
@@ -32,6 +32,9 @@ jQuery(function($) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make flash messages dissapear
|
||||||
|
setTimeout('$(".flash").fadeOut()', 5000);
|
||||||
|
|
||||||
// Highlight hovered table column
|
// Highlight hovered table column
|
||||||
$('table tbody tr td.actions a').hover(function(){
|
$('table tbody tr td.actions a').hover(function(){
|
||||||
var tr = $(this).closest('tr');
|
var tr = $(this).closest('tr');
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
// Shipments AJAX API
|
// Shipments AJAX API
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
handle_ship_click = function(){
|
||||||
|
var link = $(this);
|
||||||
|
var shipment_number = link.data('shipment-number');
|
||||||
|
var url = Spree.url( Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipment_number + "/ship.json");
|
||||||
|
$.ajax({
|
||||||
|
type: "PUT",
|
||||||
|
url: url
|
||||||
|
}).done(function( msg ) {
|
||||||
|
window.location.reload();
|
||||||
|
}).error(function( msg ) {
|
||||||
|
console.log(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$('.admin-order-edit-form a.ship').click(handle_ship_click);
|
||||||
|
|
||||||
//handle shipping method edit click
|
//handle shipping method edit click
|
||||||
$('a.edit-method').click(toggleMethodEdit);
|
$('a.edit-method').click(toggleMethodEdit);
|
||||||
$('a.cancel-method').click(toggleMethodEdit);
|
$('a.cancel-method').click(toggleMethodEdit);
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ $(document).ready(function() {
|
|||||||
if (quantity > maxQuantity) {
|
if (quantity > maxQuantity) {
|
||||||
quantity = maxQuantity;
|
quantity = maxQuantity;
|
||||||
save.parents('tr').find('input.line_item_quantity').val(maxQuantity);
|
save.parents('tr').find('input.line_item_quantity').val(maxQuantity);
|
||||||
ofnAlert(t("js.admin.orders.quantity_unavailable"));
|
ofnAlert(t("js.admin.orders.quantity_adjusted"));
|
||||||
} else {
|
|
||||||
adjustItems(shipment_number, variant_id, quantity, true);
|
|
||||||
}
|
}
|
||||||
|
toggleItemEdit();
|
||||||
|
|
||||||
|
adjustItems(shipment_number, variant_id, quantity, true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$('a.save-item').click(handle_save_click);
|
$('a.save-item').click(handle_save_click);
|
||||||
@@ -187,17 +187,18 @@ addVariantFromStockLocation = function() {
|
|||||||
$('#stock_details').hide();
|
$('#stock_details').hide();
|
||||||
|
|
||||||
var variant_id = $('input.variant_autocomplete').val();
|
var variant_id = $('input.variant_autocomplete').val();
|
||||||
var quantity = $("input.quantity").val();
|
var stock_location_id = $(this).data('stock-location-id');
|
||||||
|
var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();
|
||||||
|
|
||||||
var shipment = _.find(shipments, function(shipment){
|
var shipment = _.find(shipments, function(shipment){
|
||||||
return shipment.state == 'ready' || shipment.state == 'pending';
|
return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
|
||||||
});
|
});
|
||||||
|
|
||||||
if(shipment==undefined){
|
if(shipment==undefined){
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
url: Spree.url(Spree.routes.orders_api + "/" + order_number + "/shipments.json"),
|
url: Spree.url(Spree.routes.orders_api + "/" + order_number + "/shipments.json"),
|
||||||
data: { variant_id: variant_id, quantity: quantity }
|
data: { variant_id: variant_id, quantity: quantity, stock_location_id: stock_location_id }
|
||||||
}).done(function( msg ) {
|
}).done(function( msg ) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}).error(function( msg ) {
|
}).error(function( msg ) {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
root = exports ? this
|
||||||
|
|
||||||
|
root.taxon_tree_menu = (obj, context) ->
|
||||||
|
|
||||||
|
base_url = Spree.url(Spree.routes.taxonomy_taxons)
|
||||||
|
admin_base_url = Spree.url(Spree.routes.admin_taxonomy_taxons)
|
||||||
|
edit_url = Spree.url(Spree.routes.admin_taxonomy_taxons + '/' + obj.attr("id") + "/edit");
|
||||||
|
|
||||||
|
create:
|
||||||
|
label: "<i class='icon-plus'></i> " + Spree.translations.add,
|
||||||
|
action: (obj) -> context.create(obj)
|
||||||
|
rename:
|
||||||
|
label: "<i class='icon-pencil'></i> " + Spree.translations.rename,
|
||||||
|
action: (obj) -> context.rename(obj)
|
||||||
|
remove:
|
||||||
|
label: "<i class='icon-trash'></i> " + Spree.translations.remove,
|
||||||
|
action: (obj) -> context.remove(obj)
|
||||||
|
edit:
|
||||||
|
separator_before: true,
|
||||||
|
label: "<i class='icon-edit'></i> " + Spree.translations.edit,
|
||||||
|
action: (obj) -> window.location = edit_url.toString()
|
||||||
139
app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
Normal file
139
app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
handle_ajax_error = (XMLHttpRequest, textStatus, errorThrown) ->
|
||||||
|
$.jstree.rollback(last_rollback)
|
||||||
|
$("#ajax_error").show().html("<strong>" + server_error + "</strong><br />" + taxonomy_tree_error)
|
||||||
|
|
||||||
|
handle_move = (e, data) ->
|
||||||
|
last_rollback = data.rlbk
|
||||||
|
position = data.rslt.cp
|
||||||
|
node = data.rslt.o
|
||||||
|
new_parent = data.rslt.np
|
||||||
|
|
||||||
|
url = new URL(Spree.routes.admin_taxonomy_taxons)
|
||||||
|
url.pathname = url.pathname + '/' + node.attr("id")
|
||||||
|
data = {
|
||||||
|
_method: "put",
|
||||||
|
"taxon[position]": position,
|
||||||
|
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
|
||||||
|
}
|
||||||
|
$.ajax
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
url: url.toString(),
|
||||||
|
data: data,
|
||||||
|
error: handle_ajax_error
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
handle_create = (e, data) ->
|
||||||
|
last_rollback = data.rlbk
|
||||||
|
node = data.rslt.obj
|
||||||
|
name = data.rslt.name
|
||||||
|
position = data.rslt.position
|
||||||
|
new_parent = data.rslt.parent
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"taxon[name]": name,
|
||||||
|
"taxon[position]": position
|
||||||
|
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
|
||||||
|
}
|
||||||
|
$.ajax
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
url: base_url.toString(),
|
||||||
|
data: data,
|
||||||
|
error: handle_ajax_error,
|
||||||
|
success: (data,result) ->
|
||||||
|
node.attr('id', data.id)
|
||||||
|
|
||||||
|
handle_rename = (e, data) ->
|
||||||
|
last_rollback = data.rlbk
|
||||||
|
node = data.rslt.obj
|
||||||
|
name = data.rslt.new_name
|
||||||
|
# change the name inside the main input field as well if taxon is the root one
|
||||||
|
document.getElementById("taxonomy_name").value = name if node.parents("[id]").attr("id") == "taxonomy_tree"
|
||||||
|
|
||||||
|
url = new URL(base_url)
|
||||||
|
url.pathname = url.pathname + '/' + node.attr("id")
|
||||||
|
|
||||||
|
$.ajax
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
url: url.toString(),
|
||||||
|
data: {_method: "put", "taxon[name]": name },
|
||||||
|
error: handle_ajax_error
|
||||||
|
|
||||||
|
handle_delete = (e, data) ->
|
||||||
|
last_rollback = data.rlbk
|
||||||
|
node = data.rslt.obj
|
||||||
|
delete_url = new URL(base_url)
|
||||||
|
delete_url.pathname = delete_url.pathname + '/' + node.attr("id")
|
||||||
|
if confirm(Spree.translations.are_you_sure_delete)
|
||||||
|
$.ajax
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
url: delete_url.toString(),
|
||||||
|
data: {_method: "delete"},
|
||||||
|
error: handle_ajax_error
|
||||||
|
else
|
||||||
|
$.jstree.rollback(last_rollback)
|
||||||
|
last_rollback = null
|
||||||
|
|
||||||
|
root = exports ? this
|
||||||
|
root.setup_taxonomy_tree = (taxonomy_id) ->
|
||||||
|
if taxonomy_id != undefined
|
||||||
|
# this is defined within admin/taxonomies/edit
|
||||||
|
root.base_url = Spree.url(Spree.routes.taxonomy_taxons)
|
||||||
|
|
||||||
|
$.ajax
|
||||||
|
url: base_url.pathname.replace("/taxons", "/jstree"),
|
||||||
|
success: (taxonomy) ->
|
||||||
|
last_rollback = null
|
||||||
|
|
||||||
|
conf =
|
||||||
|
json_data:
|
||||||
|
data: taxonomy,
|
||||||
|
ajax:
|
||||||
|
url: (e) ->
|
||||||
|
base_url.pathname + '/' + e.attr('id') + '/jstree'
|
||||||
|
themes:
|
||||||
|
theme: "apple",
|
||||||
|
url: "/assets/jquery.jstree/themes/apple/style.css"
|
||||||
|
strings:
|
||||||
|
new_node: new_taxon,
|
||||||
|
loading: Spree.translations.loading + "..."
|
||||||
|
crrm:
|
||||||
|
move:
|
||||||
|
check_move: (m) ->
|
||||||
|
position = m.cp
|
||||||
|
node = m.o
|
||||||
|
new_parent = m.np
|
||||||
|
|
||||||
|
# no parent or cant drag and drop
|
||||||
|
if !new_parent || node.attr("rel") == "root"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# can't drop before root
|
||||||
|
if new_parent.attr("id") == "taxonomy_tree" && position == 0
|
||||||
|
return false
|
||||||
|
|
||||||
|
true
|
||||||
|
contextmenu:
|
||||||
|
items: (obj) ->
|
||||||
|
taxon_tree_menu(obj, this)
|
||||||
|
plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"]
|
||||||
|
|
||||||
|
$("#taxonomy_tree").jstree(conf)
|
||||||
|
.bind("move_node.jstree", handle_move)
|
||||||
|
.bind("remove.jstree", handle_delete)
|
||||||
|
.bind("create.jstree", handle_create)
|
||||||
|
.bind("rename.jstree", handle_rename)
|
||||||
|
.bind "loaded.jstree", ->
|
||||||
|
$(this).jstree("core").toggle_node($('.jstree-icon').first())
|
||||||
|
|
||||||
|
$("#taxonomy_tree a").on "dblclick", (e) ->
|
||||||
|
$("#taxonomy_tree").jstree("rename", this)
|
||||||
|
|
||||||
|
# surpress form submit on enter/return
|
||||||
|
$(document).keypress (e) ->
|
||||||
|
if e.keyCode == 13
|
||||||
|
e.preventDefault()
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, $filter, enterprise) ->
|
||||||
|
$scope.tagGroups = enterprise.tag_groups
|
||||||
|
$scope.defaultTagGroup = enterprise.default_tag_group
|
||||||
|
|
||||||
|
$scope.visibilityOptions = [ { id: "visible", name: t('js.tag_rules.visible') }, { id: "hidden", name: t('js.tag_rules.not_visible') } ]
|
||||||
|
|
||||||
|
$scope.updateRuleCounts = ->
|
||||||
|
index = $scope.defaultTagGroup.rules.length
|
||||||
|
for tagGroup in $filter('orderBy')($scope.tagGroups, 'position')
|
||||||
|
tagGroup.startIndex = index
|
||||||
|
index = index + tagGroup.rules.length
|
||||||
|
|
||||||
|
$scope.updateRuleCounts()
|
||||||
|
|
||||||
|
$scope.updateTagsRulesFor = (tagGroup) ->
|
||||||
|
for tagRule in tagGroup.rules
|
||||||
|
tagRule.preferred_customer_tags = (tag.text for tag in tagGroup.tags).join(",")
|
||||||
|
|
||||||
|
$scope.addNewRuleTo = (tagGroup, ruleType) ->
|
||||||
|
newRule =
|
||||||
|
id: null
|
||||||
|
is_default: tagGroup == $scope.defaultTagGroup
|
||||||
|
preferred_customer_tags: (tag.text for tag in tagGroup.tags).join(",")
|
||||||
|
type: "TagRule::#{ruleType}"
|
||||||
|
switch ruleType
|
||||||
|
when "FilterShippingMethods"
|
||||||
|
newRule.peferred_shipping_method_tags = []
|
||||||
|
newRule.preferred_matched_shipping_methods_visibility = "visible"
|
||||||
|
when "FilterPaymentMethods"
|
||||||
|
newRule.peferred_payment_method_tags = []
|
||||||
|
newRule.preferred_matched_payment_methods_visibility = "visible"
|
||||||
|
when "FilterProducts"
|
||||||
|
newRule.peferred_variant_tags = []
|
||||||
|
newRule.preferred_matched_variants_visibility = "visible"
|
||||||
|
when "FilterOrderCycles"
|
||||||
|
newRule.peferred_exchange_tags = []
|
||||||
|
newRule.preferred_matched_order_cycles_visibility = "visible"
|
||||||
|
tagGroup.rules.push(newRule)
|
||||||
|
$scope.updateRuleCounts()
|
||||||
|
|
||||||
|
$scope.addNewTag = ->
|
||||||
|
$scope.tagGroups.push { tags: [], rules: [], position: $scope.tagGroups.length + 1 }
|
||||||
|
|
||||||
|
$scope.deleteTagRule = (tagGroup, tagRule) ->
|
||||||
|
index = tagGroup.rules.indexOf(tagRule)
|
||||||
|
return unless index >= 0
|
||||||
|
if tagRule.id is null
|
||||||
|
tagGroup.rules.splice(index, 1)
|
||||||
|
$scope.updateRuleCounts()
|
||||||
|
else
|
||||||
|
if confirm("Are you sure?")
|
||||||
|
$http
|
||||||
|
method: "DELETE"
|
||||||
|
url: "/admin/enterprises/#{enterprise.id}/tag_rules/#{tagRule.id}.json"
|
||||||
|
.then ->
|
||||||
|
tagGroup.rules.splice(index, 1)
|
||||||
|
$scope.updateRuleCounts()
|
||||||
|
$scope.enterprise_form.$setDirty()
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
angular.module("admin.tagRules").directive "invertNumber", ->
|
||||||
|
restrict: "A"
|
||||||
|
require: "ngModel"
|
||||||
|
link: (scope, element, attrs, ngModel) ->
|
||||||
|
ngModel.$parsers.push (viewValue) ->
|
||||||
|
return -parseInt(viewValue) unless isNaN(parseInt(viewValue))
|
||||||
|
viewValue
|
||||||
|
|
||||||
|
ngModel.$formatters.push (modelValue) ->
|
||||||
|
return -parseInt(modelValue) unless isNaN(parseInt(modelValue))
|
||||||
|
modelValue
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
angular.module("admin.tagRules").directive 'newTagRuleDialog', ($rootScope, $compile, $templateCache, DialogDefaults) ->
|
||||||
|
restrict: 'A'
|
||||||
|
scope:
|
||||||
|
tagGroup: '='
|
||||||
|
addNewRuleTo: '='
|
||||||
|
link: (scope, element, attr) ->
|
||||||
|
# Compile modal template
|
||||||
|
template = $compile($templateCache.get('admin/new_tag_rule_dialog.html'))(scope)
|
||||||
|
|
||||||
|
scope.ruleTypes = [
|
||||||
|
{ id: "FilterProducts", name: t('js.tag_rules.show_hide_variants') }
|
||||||
|
{ id: "FilterShippingMethods", name: t('js.tag_rules.show_hide_shipping') }
|
||||||
|
{ id: "FilterPaymentMethods", name: t('js.tag_rules.show_hide_payment') }
|
||||||
|
{ id: "FilterOrderCycles", name: t('js.tag_rules.show_hide_order_cycles') }
|
||||||
|
]
|
||||||
|
|
||||||
|
scope.ruleType = scope.ruleTypes[0].id
|
||||||
|
|
||||||
|
# Set Dialog options
|
||||||
|
template.dialog(DialogDefaults)
|
||||||
|
|
||||||
|
# Link opening of dialog to click event on element
|
||||||
|
element.bind 'click', (e) ->
|
||||||
|
template.dialog('open')
|
||||||
|
$rootScope.$evalAsync()
|
||||||
|
|
||||||
|
scope.addRule = (tagGroup, ruleType) ->
|
||||||
|
scope.addNewRuleTo(tagGroup, ruleType)
|
||||||
|
template.dialog('close')
|
||||||
|
$rootScope.$evalAsync()
|
||||||
|
return
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
angular.module("admin.tagRules").directive "tagRule", ->
|
||||||
|
restrict: "C"
|
||||||
|
templateUrl: "admin/tag_rules/tag_rule.html"
|
||||||
|
link: (scope, element, attrs) ->
|
||||||
|
scope.opt =
|
||||||
|
"TagRule::FilterShippingMethods":
|
||||||
|
textTop: t('js.admin.tag_rules.shipping_method_tagged_top')
|
||||||
|
textBottom: t('js.admin.tag_rules.shipping_method_tagged_bottom')
|
||||||
|
taggable: "shipping_method"
|
||||||
|
tagsAttr: "shipping_method_tags"
|
||||||
|
tagListAttr: "preferred_shipping_method_tags"
|
||||||
|
inputTemplate: "admin/tag_rules/filter_shipping_methods_input.html"
|
||||||
|
tagListFor: (rule) ->
|
||||||
|
rule.preferred_shipping_method_tags
|
||||||
|
"TagRule::FilterPaymentMethods":
|
||||||
|
textTop: t('js.admin.tag_rules.payment_method_tagged_top')
|
||||||
|
textBottom: t('js.admin.tag_rules.payment_method_tagged_bottom')
|
||||||
|
taggable: "payment_method"
|
||||||
|
tagsAttr: "payment_method_tags"
|
||||||
|
tagListAttr: "preferred_payment_method_tags"
|
||||||
|
inputTemplate: "admin/tag_rules/filter_payment_methods_input.html"
|
||||||
|
tagListFor: (rule) ->
|
||||||
|
rule.preferred_payment_method_tags
|
||||||
|
"TagRule::FilterOrderCycles":
|
||||||
|
textTop: t('js.admin.tag_rules.order_cycle_tagged_top')
|
||||||
|
textBottom: t('js.admin.tag_rules.order_cycle_tagged_bottom')
|
||||||
|
taggable: "exchange"
|
||||||
|
tagsAttr: "exchange_tags"
|
||||||
|
tagListAttr: "preferred_exchange_tags"
|
||||||
|
inputTemplate: "admin/tag_rules/filter_order_cycles_input.html"
|
||||||
|
tagListFor: (rule) ->
|
||||||
|
rule.preferred_exchange_tags
|
||||||
|
"TagRule::FilterProducts":
|
||||||
|
textTop: t('js.admin.tag_rules.inventory_tagged_top')
|
||||||
|
textBottom: t('js.admin.tag_rules.inventory_tagged_bottom')
|
||||||
|
taggable: "variant"
|
||||||
|
tagsAttr: "variant_tags"
|
||||||
|
tagListAttr: "preferred_variant_tags"
|
||||||
|
inputTemplate: "admin/tag_rules/filter_products_input.html"
|
||||||
|
tagListFor: (rule) ->
|
||||||
|
rule.preferred_variant_tags
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
angular.module("admin.utils").directive "textangularLinksTargetBlank", () ->
|
||||||
|
restrict: 'CA'
|
||||||
|
link: (scope, element, attrs) ->
|
||||||
|
setTimeout ->
|
||||||
|
element.find(".ta-editor").scope().defaultTagAttributes.a.target = '_blank'
|
||||||
|
, 500
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
angular.module("admin.utils").directive "textangularStrip", () ->
|
||||||
|
restrict: 'CA'
|
||||||
|
link: (scope, element, attrs) ->
|
||||||
|
scope.stripFormatting = ($html) ->
|
||||||
|
element = document.createElement("div")
|
||||||
|
element.innerHTML = String($html)
|
||||||
|
allTags = element.getElementsByTagName("*")
|
||||||
|
for child in allTags
|
||||||
|
child.removeAttribute("style")
|
||||||
|
child.removeAttribute("class")
|
||||||
|
return element.innerHTML
|
||||||
@@ -26,8 +26,6 @@ angular.module("admin.utils").directive "variantAutocomplete", ($timeout) ->
|
|||||||
order_cycle_id: scope.order_cycle_id
|
order_cycle_id: scope.order_cycle_id
|
||||||
eligible_for_subscriptions: scope.eligible_for_subscriptions
|
eligible_for_subscriptions: scope.eligible_for_subscriptions
|
||||||
include_out_of_stock: scope.include_out_of_stock
|
include_out_of_stock: scope.include_out_of_stock
|
||||||
search_variants_as: scope.search_variants_as
|
|
||||||
order_id: scope.order_id
|
|
||||||
results: (data, page) ->
|
results: (data, page) ->
|
||||||
window.variants = data # this is how spree auto complete JS code picks up variants
|
window.variants = data # this is how spree auto complete JS code picks up variants
|
||||||
results: data
|
results: data
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ angular.module("admin.utils")
|
|||||||
$window.onbeforeunload = @onBeforeUnloadHandler
|
$window.onbeforeunload = @onBeforeUnloadHandler
|
||||||
|
|
||||||
$rootScope.$on "$locationChangeStart", @locationChangeStartHandler
|
$rootScope.$on "$locationChangeStart", @locationChangeStartHandler
|
||||||
$window.onBeforeUnloadHandler = @onBeforeUnloadHandler
|
|
||||||
|
|
||||||
# Action for regular browser navigation.
|
# Action for regular browser navigation.
|
||||||
onBeforeUnloadHandler: ($event) =>
|
onBeforeUnloadHandler: ($event) =>
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
|||||||
|
|
||||||
statusMessage:
|
statusMessage:
|
||||||
text: ""
|
text: ""
|
||||||
style: {},
|
style: {}
|
||||||
type: null,
|
|
||||||
actionName: null
|
|
||||||
|
|
||||||
invalidMessage: ""
|
invalidMessage: ""
|
||||||
|
|
||||||
@@ -25,15 +23,11 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
|||||||
active: ->
|
active: ->
|
||||||
@statusMessage.text != ''
|
@statusMessage.text != ''
|
||||||
|
|
||||||
display: (type, text, actionName = null) ->
|
display: (type, text) ->
|
||||||
@statusMessage.text = text
|
@statusMessage.text = text
|
||||||
@statusMessage.type = type
|
|
||||||
@statusMessage.actionName = actionName
|
|
||||||
@statusMessage.style = @types[type].style
|
@statusMessage.style = @types[type].style
|
||||||
null
|
null
|
||||||
|
|
||||||
clear: ->
|
clear: ->
|
||||||
@statusMessage.text = ''
|
@statusMessage.text = ''
|
||||||
@statusMessage.style = {}
|
@statusMessage.style = {}
|
||||||
@statusMessage.type = null
|
|
||||||
@statusMessage.actionName = null
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ angular.module("admin.variantOverrides").directive "trackInheritance", (VariantO
|
|||||||
require: "ngModel"
|
require: "ngModel"
|
||||||
link: (scope, element, attrs, ngModel) ->
|
link: (scope, element, attrs, ngModel) ->
|
||||||
# This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it
|
# This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it
|
||||||
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant
|
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant.id
|
||||||
|
|
||||||
ngModel.$parsers.push (viewValue) ->
|
ngModel.$parsers.push (viewValue) ->
|
||||||
if ngModel.$dirty && viewValue
|
if ngModel.$dirty && viewValue
|
||||||
DirtyVariantOverrides.inherit scope.hub_id, scope.variant, scope.variantOverrides[scope.hub_id][scope.variant.id].id
|
DirtyVariantOverrides.inherit scope.hub_id, scope.variant.id, scope.variantOverrides[scope.hub_id][scope.variant.id].id
|
||||||
scope.displayDirty()
|
scope.displayDirty()
|
||||||
viewValue
|
viewValue
|
||||||
|
|||||||
@@ -2,8 +2,4 @@ angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
|
|||||||
return (products, hubPermissions, hub_id) ->
|
return (products, hubPermissions, hub_id) ->
|
||||||
return [] if !hub_id
|
return [] if !hub_id
|
||||||
return [] if !hubPermissions[hub_id]
|
return [] if !hubPermissions[hub_id]
|
||||||
|
return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)
|
||||||
return $filter('filter')(products, ((product) ->
|
|
||||||
for variant in product.variants
|
|
||||||
return hubPermissions[hub_id].indexOf(variant.producer_id) > -1
|
|
||||||
), true)
|
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http
|
|||||||
@add(hub_id, variant_id, vo_id)
|
@add(hub_id, variant_id, vo_id)
|
||||||
@dirtyVariantOverrides[hub_id][variant_id][attr] = value
|
@dirtyVariantOverrides[hub_id][variant_id][attr] = value
|
||||||
|
|
||||||
inherit: (hub_id, variant, vo_id) ->
|
inherit: (hub_id, variant_id, vo_id) ->
|
||||||
@add(hub_id, variant.id, vo_id)
|
@add(hub_id, variant_id, vo_id)
|
||||||
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant))
|
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant_id))
|
||||||
delete blankVo[attr] for attr, value of blankVo when attr not in @requiredAttrs()
|
delete blankVo[attr] for attr, value of blankVo when attr not in @requiredAttrs()
|
||||||
@dirtyVariantOverrides[hub_id][variant.id] = blankVo
|
@dirtyVariantOverrides[hub_id][variant_id] = blankVo
|
||||||
|
|
||||||
count: ->
|
count: ->
|
||||||
count = 0
|
count = 0
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user