mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-12 18:36:49 +00:00
Compare commits
298 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7415503b63 | ||
|
|
cc35d118eb | ||
|
|
025f8b25b1 | ||
|
|
3017f61047 | ||
|
|
8cb3d06f7c | ||
|
|
8f442e82ed | ||
|
|
b28886dd38 | ||
|
|
bd4f115185 | ||
|
|
c43650034f | ||
|
|
94bc787283 | ||
|
|
58851a8e67 | ||
|
|
c3e2382600 | ||
|
|
802878b4eb | ||
|
|
69d8fc3cad | ||
|
|
6a226e476d | ||
|
|
479d52a2bb | ||
|
|
73688b9544 | ||
|
|
02ea3cb61c | ||
|
|
c7d0594257 | ||
|
|
4eee7ad603 | ||
|
|
23f7f2974a | ||
|
|
8105b919e0 | ||
|
|
53ef5148e9 | ||
|
|
93e6f9034c | ||
|
|
125a92346c | ||
|
|
31b8fe16cb | ||
|
|
cbffea8d30 | ||
|
|
be9da62d98 | ||
|
|
7320fa3f09 | ||
|
|
9f6c149735 | ||
|
|
50578647ee | ||
|
|
a28f05fddc | ||
|
|
c4c266246c | ||
|
|
d6c044fd5b | ||
|
|
a75ea5b506 | ||
|
|
7f937fd4b1 | ||
|
|
b4a64185dd | ||
|
|
a905acb56e | ||
|
|
067fa80d0f | ||
|
|
667ce5eda2 | ||
|
|
980cc9c724 | ||
|
|
c72f9477cd | ||
|
|
8787eed863 | ||
|
|
e2b6199f26 | ||
|
|
49d345e608 | ||
|
|
12a6f9ac40 | ||
|
|
7d4efe75c3 | ||
|
|
2cd41f3b8f | ||
|
|
436d919fc3 | ||
|
|
7a12e7426f | ||
|
|
bd58969fb5 | ||
|
|
0a385cc67c | ||
|
|
be5a630b9d | ||
|
|
98889365f1 | ||
|
|
9227660faf | ||
|
|
bdafc1ff02 | ||
|
|
9457b0505d | ||
|
|
755116e713 | ||
|
|
4852ee2c6e | ||
|
|
9ba215316b | ||
|
|
084f7a8a47 | ||
|
|
7c60dfb75c | ||
|
|
e2410105ce | ||
|
|
8b0207f4b1 | ||
|
|
d3319cfd69 | ||
|
|
378b5e656e | ||
|
|
b2da57b496 | ||
|
|
c6a34cfe34 | ||
|
|
591a279927 | ||
|
|
86774b3e4e | ||
|
|
2761cee5e6 | ||
|
|
9460d17417 | ||
|
|
404c07a590 | ||
|
|
f2f0d954c6 | ||
|
|
1028d42e35 | ||
|
|
91ad63d1ed | ||
|
|
2780ae78f7 | ||
|
|
bf661159c6 | ||
|
|
ad78ef14ef | ||
|
|
c7efa43cdb | ||
|
|
df6e553661 | ||
|
|
4d59343f6c | ||
|
|
44d29e98e0 | ||
|
|
06c27d6aaf | ||
|
|
cb9edfaed8 | ||
|
|
51a3085452 | ||
|
|
8ccceccd92 | ||
|
|
5e58f11006 | ||
|
|
5d8ecc5e5c | ||
|
|
12e70d729a | ||
|
|
dc61580da1 | ||
|
|
b956d6f21b | ||
|
|
b5e3681eab | ||
|
|
c45ac93a12 | ||
|
|
60ee33053d | ||
|
|
a4ea311439 | ||
|
|
5b383237ea | ||
|
|
de8029f877 | ||
|
|
d818162a9f | ||
|
|
9bd4d29027 | ||
|
|
742d442929 | ||
|
|
f08f744077 | ||
|
|
72ab0ba3f5 | ||
|
|
bb4b483469 | ||
|
|
585073a326 | ||
|
|
c3189892af | ||
|
|
417011909c | ||
|
|
9ed612410f | ||
|
|
7098cf2224 | ||
|
|
4713e9046c | ||
|
|
037030cf60 | ||
|
|
286f05d05c | ||
|
|
e9a750ce6d | ||
|
|
8942f3c72b | ||
|
|
23b2c8e11b | ||
|
|
9a9e9763cc | ||
|
|
90cd2e0ba2 | ||
|
|
8c02bde7f2 | ||
|
|
8c1e0bae92 | ||
|
|
09c7288b11 | ||
|
|
d27ffe5fca | ||
|
|
6c94650e51 | ||
|
|
79bb469332 | ||
|
|
2c4df63879 | ||
|
|
9f5d73184f | ||
|
|
4a5938c0f7 | ||
|
|
f414e04dea | ||
|
|
ef4d3ec138 | ||
|
|
0a9eb173ea | ||
|
|
f5a9ec7fa9 | ||
|
|
e190b87f12 | ||
|
|
ff2e0f4d45 | ||
|
|
d50bcbb70a | ||
|
|
782f813a15 | ||
|
|
9b0545c33f | ||
|
|
55f162ff4a | ||
|
|
ede7650fc9 | ||
|
|
7631fd422e | ||
|
|
c2c2a9503c | ||
|
|
bc1823e276 | ||
|
|
01d5830480 | ||
|
|
bad7369e67 | ||
|
|
ab65b2d745 | ||
|
|
f38aa73434 | ||
|
|
3862e0206c | ||
|
|
3a0722f39c | ||
|
|
42f7f2606b | ||
|
|
9d9f7e8717 | ||
|
|
0cf244b211 | ||
|
|
646ba18b8a | ||
|
|
8bd631fbb7 | ||
|
|
5c3acf38d7 | ||
|
|
afdc40d230 | ||
|
|
771573af1c | ||
|
|
953122b6f6 | ||
|
|
75325e2935 | ||
|
|
7e48007d09 | ||
|
|
50ab0a494c | ||
|
|
d3ef744daf | ||
|
|
ccdd12bf59 | ||
|
|
b66b033999 | ||
|
|
35d37639af | ||
|
|
6790cad089 | ||
|
|
7087d1b290 | ||
|
|
8f0cdf8722 | ||
|
|
25f6db09a5 | ||
|
|
11006c3a60 | ||
|
|
b2a3715a8b | ||
|
|
693789d526 | ||
|
|
d26b407801 | ||
|
|
6d284023fe | ||
|
|
570b72868b | ||
|
|
286d9f8e7d | ||
|
|
b0c3265cdb | ||
|
|
6bb709e85e | ||
|
|
fe257162b7 | ||
|
|
b510736a8d | ||
|
|
2df0078ea9 | ||
|
|
ca079e6e26 | ||
|
|
ac06126f59 | ||
|
|
aecb5f49c9 | ||
|
|
a18fd54916 | ||
|
|
626b802ea7 | ||
|
|
28ab41c47f | ||
|
|
17a85e9c1c | ||
|
|
9e746d1b40 | ||
|
|
273f78b214 | ||
|
|
bd1d9892a2 | ||
|
|
cb825df75b | ||
|
|
bfcadfd7c0 | ||
|
|
255b5f1cd5 | ||
|
|
dffcd446fd | ||
|
|
1987f0b667 | ||
|
|
0b5efae8c4 | ||
|
|
36bb7cb317 | ||
|
|
49dbe1d039 | ||
|
|
c326aa6b23 | ||
|
|
ec91d717c7 | ||
|
|
da843d1ba1 | ||
|
|
2c4b3ab8fc | ||
|
|
1c7fbd1d2d | ||
|
|
8042dac74f | ||
|
|
ad1ce00223 | ||
|
|
d916ed2c96 | ||
|
|
da66a2947c | ||
|
|
646d3b8ed9 | ||
|
|
1f15f094ce | ||
|
|
adddee2c3c | ||
|
|
74e7bd5172 | ||
|
|
66859f44ca | ||
|
|
6f7a547e15 | ||
|
|
c057c72321 | ||
|
|
7a3b4d394b | ||
|
|
32e3fc0175 | ||
|
|
23c9410a25 | ||
|
|
7e9c5ea58b | ||
|
|
6c313a1b5a | ||
|
|
244a88a1cd | ||
|
|
589315780c | ||
|
|
1654bb2b0a | ||
|
|
9f396a40b7 | ||
|
|
4bf1b7ac08 | ||
|
|
2910082584 | ||
|
|
70b5fda632 | ||
|
|
8bc82685ae | ||
|
|
63125705ac | ||
|
|
9bf2dad343 | ||
|
|
05b3417f77 | ||
|
|
403aa6ac6f | ||
|
|
fbad3ee9f4 | ||
|
|
ddb8b2d08f | ||
|
|
42c9ee033a | ||
|
|
524634b4ea | ||
|
|
0b97171bb0 | ||
|
|
b0c7e29b0d | ||
|
|
3d7799df19 | ||
|
|
5f02d88a86 | ||
|
|
bdae8e6478 | ||
|
|
053ef05baf | ||
|
|
7fcb31d563 | ||
|
|
31a7374808 | ||
|
|
e5ce06ae39 | ||
|
|
5f64204d51 | ||
|
|
94b75540e4 | ||
|
|
6e489d7770 | ||
|
|
81b1169e77 | ||
|
|
4b558b4820 | ||
|
|
e224b8f63b | ||
|
|
80bb0606b4 | ||
|
|
499fcc791e | ||
|
|
30dae3c3ea | ||
|
|
af247c32a3 | ||
|
|
6f9dcf7e27 | ||
|
|
2d064bab64 | ||
|
|
b69eb9bdff | ||
|
|
f79c1879bd | ||
|
|
646d538a3d | ||
|
|
b95d798a27 | ||
|
|
e1e4aeac1f | ||
|
|
c7ae47053e | ||
|
|
5892ae1800 | ||
|
|
d3a2c09f66 | ||
|
|
d596e692d8 | ||
|
|
2df95dcbab | ||
|
|
7aa9b164e6 | ||
|
|
74368f939b | ||
|
|
cb02cd39fe | ||
|
|
118e18a78e | ||
|
|
cbced144d5 | ||
|
|
1d2115766a | ||
|
|
6814ef43f4 | ||
|
|
c9e8294561 | ||
|
|
82d0e1bf68 | ||
|
|
b16e541a81 | ||
|
|
c12d494de3 | ||
|
|
9be27842e1 | ||
|
|
2a7754edbf | ||
|
|
cfeafbfc51 | ||
|
|
05b00f16ad | ||
|
|
78fdaa68c8 | ||
|
|
e8813833fa | ||
|
|
a5f44cb9b2 | ||
|
|
97d21d8cbe | ||
|
|
7afdd13b64 | ||
|
|
54c446f0a3 | ||
|
|
4454c90575 | ||
|
|
dd3a61acdf | ||
|
|
6d8ddd1eda | ||
|
|
b8e8ab15d1 | ||
|
|
bf1d2f3620 | ||
|
|
43026ddc6a | ||
|
|
18b83d2423 | ||
|
|
3750898c44 | ||
|
|
d34f8900d7 | ||
|
|
addf36a304 | ||
|
|
6a912b7d8c | ||
|
|
8011449ce7 | ||
|
|
be0894653a |
@@ -20,7 +20,6 @@ STRIPE_INSTANCE_SECRET_KEY="bogus_key"
|
||||
STRIPE_CUSTOMER="bogus_customer"
|
||||
STRIPE_ACCOUNT="bogus_account"
|
||||
STRIPE_CLIENT_ID="bogus_client_id"
|
||||
STRIPE_PUBLIC_TEST_API_KEY="bogus_stripe_publishable_key"
|
||||
|
||||
SITE_URL="test.host"
|
||||
|
||||
|
||||
15
.github/test-events/dependabot-pr.json
vendored
Normal file
15
.github/test-events/dependabot-pr.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
2
.github/workflows/linters.yml
vendored
2
.github/workflows/linters.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Linters
|
||||
on: [push, pull_request]
|
||||
on: [pull_request]
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
jobs:
|
||||
|
||||
151
.github/workflows/move-dependency-pr-to-code-review.yml
vendored
Normal file
151
.github/workflows/move-dependency-pr-to-code-review.yml
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
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}'`);
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -59,3 +59,4 @@ yarn-debug.log*
|
||||
|
||||
/config/credentials.yml.enc
|
||||
/config/master.key
|
||||
.secrets
|
||||
|
||||
@@ -47,7 +47,7 @@ Metrics/BlockNesting:
|
||||
Exclude:
|
||||
- 'app/models/spree/payment/processing.rb'
|
||||
|
||||
# Offense count: 47
|
||||
# Offense count: 48
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Exclude:
|
||||
@@ -88,6 +88,7 @@ Metrics/ClassLength:
|
||||
- 'app/services/cart_service.rb'
|
||||
- 'app/services/order_cycles/form_service.rb'
|
||||
- 'app/services/orders/sync_service.rb'
|
||||
- 'app/services/permissions/order.rb'
|
||||
- 'app/services/sets/product_set.rb'
|
||||
- 'engines/order_management/app/services/order_management/order/updater.rb'
|
||||
- 'lib/open_food_network/enterprise_fee_calculator.rb'
|
||||
@@ -98,7 +99,6 @@ Metrics/ClassLength:
|
||||
- 'lib/reporting/reports/enterprise_fee_summary/enterprise_fees_with_tax_report_by_producer.rb'
|
||||
- 'lib/reporting/reports/enterprise_fee_summary/scope.rb'
|
||||
- 'lib/reporting/reports/xero_invoices/base.rb'
|
||||
- 'app/services/permissions/order.rb'
|
||||
|
||||
# Offense count: 30
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
||||
@@ -129,14 +129,13 @@ Metrics/CyclomaticComplexity:
|
||||
- 'lib/spree/localized_number.rb'
|
||||
- 'spec/models/product_importer_spec.rb'
|
||||
|
||||
# Offense count: 23
|
||||
# Offense count: 22
|
||||
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
||||
Metrics/MethodLength:
|
||||
Exclude:
|
||||
- 'app/controllers/admin/enterprises_controller.rb'
|
||||
- 'app/controllers/payment_gateways/paypal_controller.rb'
|
||||
- 'app/controllers/spree/orders_controller.rb'
|
||||
- 'app/helpers/spree/admin/navigation_helper.rb'
|
||||
- 'app/models/spree/ability.rb'
|
||||
- 'app/models/spree/gateway/pay_pal_express.rb'
|
||||
- 'app/models/spree/order/checkout.rb'
|
||||
@@ -149,7 +148,7 @@ Metrics/MethodLength:
|
||||
- 'lib/spree/localized_number.rb'
|
||||
- 'lib/tasks/sample_data/product_factory.rb'
|
||||
|
||||
# Offense count: 47
|
||||
# Offense count: 10
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ModuleLength:
|
||||
Exclude:
|
||||
@@ -174,7 +173,7 @@ Metrics/ParameterLists:
|
||||
- 'spec/support/controller_requests_helper.rb'
|
||||
- 'spec/system/admin/reports_spec.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# Offense count: 4
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
||||
Metrics/PerceivedComplexity:
|
||||
Exclude:
|
||||
@@ -182,6 +181,27 @@ Metrics/PerceivedComplexity:
|
||||
- 'app/models/spree/ability.rb'
|
||||
- 'app/models/spree/order/checkout.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: EnforcedStyle, AllowedPatterns.
|
||||
# SupportedStyles: snake_case, camelCase
|
||||
Naming/MethodName:
|
||||
Exclude:
|
||||
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
||||
# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
|
||||
Naming/MethodParameterName:
|
||||
Exclude:
|
||||
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns.
|
||||
# SupportedStyles: snake_case, camelCase
|
||||
Naming/VariableName:
|
||||
Exclude:
|
||||
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: TransactionMethods.
|
||||
Rails/TransactionExitStatement:
|
||||
|
||||
4
.secrets.example
Normal file
4
.secrets.example
Normal file
@@ -0,0 +1,4 @@
|
||||
# .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-----"
|
||||
11
.simplecov
11
.simplecov
@@ -2,10 +2,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
SimpleCov.start 'rails' do
|
||||
# The rails profile contains some filters already:
|
||||
#
|
||||
# - "/test/"
|
||||
# - "/features/"
|
||||
# - "/spec/"
|
||||
# - "/autotest/"
|
||||
# - /^\/config\//
|
||||
# - /^\/db\//
|
||||
add_filter '/bin/'
|
||||
add_filter '/config/'
|
||||
add_filter '/config/' # to include engine config
|
||||
add_filter '/script'
|
||||
add_filter '/db'
|
||||
|
||||
formatter SimpleCov::Formatter::SimpleFormatter
|
||||
end
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
#!/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
-c master
|
||||
--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/*"
|
||||
|
||||
@@ -2,7 +2,9 @@ FROM ruby:3.1.4-alpine3.19 AS base
|
||||
ENV LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
TZ=Europe/London \
|
||||
RAILS_ROOT=/usr/src/app
|
||||
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
|
||||
@@ -14,7 +16,7 @@ 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 \
|
||||
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
|
||||
|
||||
@@ -25,7 +25,8 @@ RUN apt-get update && apt-get install -y \
|
||||
libjemalloc-dev \
|
||||
libssl-dev \
|
||||
ca-certificates \
|
||||
gnupg
|
||||
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
|
||||
|
||||
@@ -73,7 +73,7 @@ To login as the default user, use:
|
||||
email: ofn@example.com
|
||||
password: ofn123
|
||||
|
||||
Seee [Locale and sample data] about loading data.
|
||||
See [Locale and sample data] about loading data.
|
||||
|
||||
### Testing
|
||||
|
||||
|
||||
5
Gemfile
5
Gemfile
@@ -20,6 +20,10 @@ gem 'ransack', '~> 4.1.0'
|
||||
gem 'responders'
|
||||
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-js', '~> 3.9.0'
|
||||
gem 'rails-i18n'
|
||||
@@ -167,7 +171,6 @@ group :test, :development do
|
||||
gem 'rswag'
|
||||
gem 'shoulda-matchers'
|
||||
gem 'stimulus_reflex_testing', github: "podia/stimulus_reflex_testing", branch: :main
|
||||
gem 'timecop'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
||||
161
Gemfile.lock
161
Gemfile.lock
@@ -122,7 +122,7 @@ GEM
|
||||
activemodel (= 7.1.5.2)
|
||||
activesupport (= 7.1.5.2)
|
||||
timeout (>= 0.4.0)
|
||||
activerecord-import (1.6.0)
|
||||
activerecord-import (2.2.0)
|
||||
activerecord (>= 4.2)
|
||||
activerecord-postgresql-adapter (0.0.1)
|
||||
pg
|
||||
@@ -156,8 +156,8 @@ GEM
|
||||
activerecord (>= 6.1, < 7.2)
|
||||
acts_as_list (1.0.4)
|
||||
activerecord (>= 4.2)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
afm (0.2.2)
|
||||
angular-rails-templates (1.2.1)
|
||||
@@ -200,7 +200,7 @@ GEM
|
||||
msgpack (~> 1.2)
|
||||
bugsnag (6.26.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
builder (3.2.4)
|
||||
builder (3.3.0)
|
||||
bullet (8.0.8)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
@@ -228,7 +228,7 @@ GEM
|
||||
marcel (~> 1.0)
|
||||
nokogiri (~> 1.10, >= 1.10.4)
|
||||
rubyzip (>= 1.3.0, < 3)
|
||||
cgi (0.3.7)
|
||||
cgi (0.5.0)
|
||||
childprocess (5.0.0)
|
||||
choice (0.2.0)
|
||||
chronic (0.10.2)
|
||||
@@ -244,7 +244,7 @@ GEM
|
||||
matrix
|
||||
ruby-rc4 (>= 0.1.5)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.3)
|
||||
connection_pool (2.5.4)
|
||||
cookiejar (0.3.4)
|
||||
crack (1.0.0)
|
||||
bigdecimal
|
||||
@@ -281,9 +281,9 @@ GEM
|
||||
devise (>= 4.9.0)
|
||||
devise-token_authenticatable (1.1.0)
|
||||
devise (>= 4.0.0, < 5.0.0)
|
||||
diff-lcs (1.5.1)
|
||||
digest (3.1.1)
|
||||
docile (1.4.0)
|
||||
diff-lcs (1.6.2)
|
||||
digest (3.2.0)
|
||||
docile (1.4.1)
|
||||
dotenv (3.1.2)
|
||||
drb (2.2.3)
|
||||
em-http-request (1.1.7)
|
||||
@@ -299,7 +299,9 @@ GEM
|
||||
eventmachine (>= 1.0.0.beta.1)
|
||||
email_validator (2.2.4)
|
||||
activemodel
|
||||
erubi (1.12.0)
|
||||
erb (4.0.4)
|
||||
cgi (>= 0.3.3)
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.3.0)
|
||||
tzinfo
|
||||
eventmachine (1.2.7)
|
||||
@@ -324,7 +326,7 @@ GEM
|
||||
websocket-driver (>= 0.6, < 0.8)
|
||||
ffaker (2.23.0)
|
||||
ffi (1.16.3)
|
||||
flipper (1.3.0)
|
||||
flipper (1.3.6)
|
||||
concurrent-ruby (< 2)
|
||||
flipper-active_record (1.3.0)
|
||||
activerecord (>= 4.2, < 8)
|
||||
@@ -372,7 +374,7 @@ GEM
|
||||
temple (>= 0.8.2)
|
||||
thor
|
||||
tilt
|
||||
hashdiff (1.1.0)
|
||||
hashdiff (1.2.1)
|
||||
hashery (2.1.2)
|
||||
hashie (5.0.0)
|
||||
highline (2.0.3)
|
||||
@@ -401,10 +403,11 @@ GEM
|
||||
activerecord (>= 3.0)
|
||||
invisible_captcha (2.3.0)
|
||||
rails (>= 5.2)
|
||||
io-console (0.7.2)
|
||||
io-console (0.8.1)
|
||||
ipaddress (0.8.3)
|
||||
irb (1.12.0)
|
||||
rdoc
|
||||
irb (1.15.2)
|
||||
pp (>= 0.6.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
jquery-rails (4.4.0)
|
||||
@@ -463,16 +466,17 @@ GEM
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
method_source (1.1.0)
|
||||
mime-types (3.5.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2023.1205)
|
||||
mime-types (3.7.0)
|
||||
logger
|
||||
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
||||
mime-types-data (3.2025.0924)
|
||||
mimemagic (0.4.3)
|
||||
nokogiri (~> 1)
|
||||
rake
|
||||
mini_magick (4.11.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.6)
|
||||
minitest (5.25.5)
|
||||
minitest (5.26.0)
|
||||
monetize (1.13.0)
|
||||
money (~> 6.12)
|
||||
money (6.16.0)
|
||||
@@ -492,9 +496,9 @@ GEM
|
||||
timeout
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
newrelic_rpm (9.9.0)
|
||||
newrelic_rpm (9.22.0)
|
||||
nio4r (2.7.1)
|
||||
nokogiri (1.18.9)
|
||||
nokogiri (1.18.10)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri-html5-inference (0.3.0)
|
||||
@@ -538,7 +542,7 @@ GEM
|
||||
parallel (1.24.0)
|
||||
paranoia (2.6.3)
|
||||
activerecord (>= 5.1, < 7.2)
|
||||
parser (3.3.8.0)
|
||||
parser (3.3.9.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
paypal-sdk-core (0.3.4)
|
||||
@@ -553,13 +557,17 @@ GEM
|
||||
ruby-rc4
|
||||
ttfunk
|
||||
pg (1.2.3)
|
||||
pp (0.6.2)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
private_address_check (0.5.0)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
psych (5.1.2)
|
||||
psych (5.2.6)
|
||||
date
|
||||
stringio
|
||||
public_suffix (5.0.5)
|
||||
public_suffix (6.0.2)
|
||||
puffing-billy (4.0.2)
|
||||
addressable (~> 2.5)
|
||||
em-http-request (~> 1.1, >= 1.1.0)
|
||||
@@ -575,7 +583,7 @@ GEM
|
||||
railties (>= 4.2)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (2.2.14)
|
||||
rack (2.2.20)
|
||||
rack-mini-profiler (2.3.4)
|
||||
rack (>= 1.2.0)
|
||||
rack-oauth2 (2.2.1)
|
||||
@@ -593,7 +601,7 @@ GEM
|
||||
rack-rewrite (1.5.1)
|
||||
rack-session (1.0.2)
|
||||
rack (< 3)
|
||||
rack-test (2.1.0)
|
||||
rack-test (2.2.0)
|
||||
rack (>= 1.3)
|
||||
rack-timeout (0.7.0)
|
||||
rackup (1.0.1)
|
||||
@@ -617,7 +625,7 @@ GEM
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.2.0)
|
||||
rails-dom-testing (2.3.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
@@ -626,10 +634,10 @@ GEM
|
||||
activesupport (>= 4.2)
|
||||
choice (~> 0.2.0)
|
||||
ruby-graphviz (~> 1.2)
|
||||
rails-html-sanitizer (1.6.1)
|
||||
rails-html-sanitizer (1.6.2)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||
rails-i18n (7.0.9)
|
||||
rails-i18n (7.0.10)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
rails_safe_tasks (1.0.0)
|
||||
@@ -642,7 +650,7 @@ GEM
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.2.1)
|
||||
rake (13.3.0)
|
||||
ransack (4.1.1)
|
||||
activerecord (>= 6.1.5)
|
||||
activesupport (>= 6.1.5)
|
||||
@@ -654,15 +662,17 @@ GEM
|
||||
bcp47_spec (~> 0.2)
|
||||
bigdecimal (~> 3.1, >= 3.1.5)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdoc (6.6.3.1)
|
||||
rdoc (6.15.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
redcarpet (3.6.0)
|
||||
redis (5.2.0)
|
||||
redis (5.4.1)
|
||||
redis-client (>= 0.22.0)
|
||||
redis-client (0.22.1)
|
||||
redis-client (0.26.1)
|
||||
connection_pool
|
||||
regexp_parser (2.9.2)
|
||||
reline (0.5.0)
|
||||
reline (0.6.2)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
@@ -687,18 +697,18 @@ GEM
|
||||
rspec-core (~> 3.13.0)
|
||||
rspec-expectations (~> 3.13.0)
|
||||
rspec-mocks (~> 3.13.0)
|
||||
rspec-core (3.13.0)
|
||||
rspec-core (3.13.5)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.0)
|
||||
rspec-expectations (3.13.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.0)
|
||||
rspec-mocks (3.13.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (6.1.2)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
rspec-rails (7.1.1)
|
||||
actionpack (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
rspec-core (~> 3.13)
|
||||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
@@ -708,22 +718,22 @@ GEM
|
||||
rspec-sql (0.0.3)
|
||||
activesupport
|
||||
rspec
|
||||
rspec-support (3.13.1)
|
||||
rswag (2.13.0)
|
||||
rswag-api (= 2.13.0)
|
||||
rswag-specs (= 2.13.0)
|
||||
rswag-ui (= 2.13.0)
|
||||
rswag-api (2.13.0)
|
||||
activesupport (>= 3.1, < 7.2)
|
||||
railties (>= 3.1, < 7.2)
|
||||
rswag-specs (2.13.0)
|
||||
activesupport (>= 3.1, < 7.2)
|
||||
json-schema (>= 2.2, < 5.0)
|
||||
railties (>= 3.1, < 7.2)
|
||||
rspec-support (3.13.6)
|
||||
rswag (2.16.0)
|
||||
rswag-api (= 2.16.0)
|
||||
rswag-specs (= 2.16.0)
|
||||
rswag-ui (= 2.16.0)
|
||||
rswag-api (2.16.0)
|
||||
activesupport (>= 5.2, < 8.1)
|
||||
railties (>= 5.2, < 8.1)
|
||||
rswag-specs (2.16.0)
|
||||
activesupport (>= 5.2, < 8.1)
|
||||
json-schema (>= 2.2, < 6.0)
|
||||
railties (>= 5.2, < 8.1)
|
||||
rspec-core (>= 2.14)
|
||||
rswag-ui (2.13.0)
|
||||
actionpack (>= 3.1, < 7.2)
|
||||
railties (>= 3.1, < 7.2)
|
||||
rswag-ui (2.16.0)
|
||||
actionpack (>= 5.2, < 8.1)
|
||||
railties (>= 5.2, < 8.1)
|
||||
rubocop (1.64.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
@@ -791,7 +801,7 @@ GEM
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov-html (0.13.2)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
spreadsheet_architect (5.0.0)
|
||||
caxlsx (>= 3.3.0, < 4)
|
||||
@@ -801,20 +811,21 @@ GEM
|
||||
spring (>= 0.9.1)
|
||||
spring-commands-rubocop (0.4.0)
|
||||
spring (>= 1.0)
|
||||
sprockets (3.7.2)
|
||||
sprockets (3.7.5)
|
||||
base64
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
state_machines (0.6.0)
|
||||
state_machines-activemodel (0.9.0)
|
||||
activemodel (>= 6.0)
|
||||
state_machines (>= 0.6.0)
|
||||
state_machines-activerecord (0.9.0)
|
||||
activerecord (>= 6.0)
|
||||
state_machines-activemodel (>= 0.9.0)
|
||||
state_machines (0.20.0)
|
||||
state_machines-activemodel (0.10.0)
|
||||
activemodel (>= 7.1)
|
||||
state_machines (>= 0.10.0)
|
||||
state_machines-activerecord (0.31.0)
|
||||
activerecord (>= 7.1)
|
||||
state_machines-activemodel (>= 0.10.0)
|
||||
stimulus_reflex (3.5.5)
|
||||
actioncable (>= 5.2)
|
||||
actionpack (>= 5.2)
|
||||
@@ -828,9 +839,9 @@ GEM
|
||||
railties (>= 5.2)
|
||||
redis (>= 4.0, < 6.0)
|
||||
stringex (2.8.6)
|
||||
stringio (3.1.0)
|
||||
stringio (3.1.7)
|
||||
stripe (11.1.0)
|
||||
strscan (3.1.2)
|
||||
strscan (3.1.5)
|
||||
swd (2.0.3)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
@@ -842,8 +853,8 @@ GEM
|
||||
thor (1.4.0)
|
||||
thread-local (1.1.0)
|
||||
tilt (2.3.0)
|
||||
timecop (0.9.10)
|
||||
timeout (0.4.3)
|
||||
tsort (0.2.0)
|
||||
ttfunk (1.8.0)
|
||||
bigdecimal (~> 3.1)
|
||||
turbo-rails (2.0.5)
|
||||
@@ -854,7 +865,7 @@ GEM
|
||||
turbo-rails (>= 1.3.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
undercover (0.7.4)
|
||||
undercover (0.8.1)
|
||||
base64
|
||||
bigdecimal
|
||||
imagen (>= 0.2.0)
|
||||
@@ -895,7 +906,7 @@ GEM
|
||||
activesupport
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
webmock (3.23.1)
|
||||
webmock (3.25.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
@@ -904,7 +915,7 @@ GEM
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webrick (1.8.2)
|
||||
webrick (1.9.1)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
@@ -912,11 +923,11 @@ GEM
|
||||
chronic (>= 0.6.3)
|
||||
wicked_pdf (2.8.1)
|
||||
activesupport
|
||||
wkhtmltopdf-binary (0.12.6.7)
|
||||
wkhtmltopdf-binary (0.12.6.10)
|
||||
xml-simple (1.1.8)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.15)
|
||||
zeitwerk (2.6.18)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@@ -1044,12 +1055,12 @@ DEPENDENCIES
|
||||
spring
|
||||
spring-commands-rspec
|
||||
spring-commands-rubocop
|
||||
sprockets (~> 3.7)
|
||||
state_machines-activerecord
|
||||
stimulus_reflex
|
||||
stimulus_reflex_testing!
|
||||
stringex (~> 2.8.5)
|
||||
stripe
|
||||
timecop
|
||||
turbo-rails
|
||||
turbo_power
|
||||
undercover
|
||||
|
||||
@@ -6,7 +6,6 @@ angular.module("admin.enterprises", [
|
||||
"admin.side_menu",
|
||||
"admin.taxons",
|
||||
'admin.indexUtils',
|
||||
'admin.tagRules',
|
||||
'admin.dropdown',
|
||||
'ngSanitize']
|
||||
)
|
||||
|
||||
@@ -15,4 +15,4 @@ angular.module("admin.paymentMethods").controller "StripeController", ($scope, $
|
||||
permalink = shops.filter((shop) ->
|
||||
shop.id == $scope.paymentMethod.preferred_enterprise_id
|
||||
)[0].permalink
|
||||
"/admin/enterprises/#{permalink}/edit#/payment_methods"
|
||||
"/admin/enterprises/#{permalink}/edit#/payment_methods_panel"
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
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()
|
||||
@@ -1,11 +0,0 @@
|
||||
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
|
||||
@@ -1,26 +0,0 @@
|
||||
angular.module("admin.tagRules").directive 'newTagRuleDialog', ($rootScope, $compile, $templateCache, DialogDefaults, ruleTypes) ->
|
||||
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 = ruleTypes
|
||||
|
||||
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
|
||||
@@ -1,41 +0,0 @@
|
||||
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
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module('Darkswarm').directive "activeTableHubLink", (CurrentHub, CurrentOrder) ->
|
||||
# Change the text of the hub link based on CurrentHub
|
||||
# To be used with ofnEmptiesCart
|
||||
# To be used with ofnChangeHub
|
||||
# Takes "change" and "shop" as text string attributes
|
||||
restrict: "A"
|
||||
scope:
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
angular.module('Darkswarm').directive "darkerBackground", ->
|
||||
restrict: "A"
|
||||
link: (scope, elm, attr)->
|
||||
toggleClass = (value) ->
|
||||
elm.closest('.page-view').toggleClass("with-darker-background", value)
|
||||
|
||||
toggleClass(true)
|
||||
|
||||
# if an OrderCycle is selected, disable darker background
|
||||
scope.$watch 'order_cycle.order_cycle_id', (newvalue, oldvalue) ->
|
||||
toggleClass(false) if newvalue
|
||||
@@ -1,14 +0,0 @@
|
||||
# Allows disabling of link buttons via disabled attribute.
|
||||
# This is normally ignored, ie the link appears disabled but is still clickable.
|
||||
|
||||
angular.module('Darkswarm').directive "disableDynamically", ->
|
||||
restrict: 'A'
|
||||
|
||||
link: (scope, element, attrs) ->
|
||||
element.on 'click', (e) ->
|
||||
if attrs.disabled
|
||||
e.preventDefault()
|
||||
return
|
||||
|
||||
scope.$on "$destroy", ->
|
||||
element.off("click")
|
||||
@@ -1,7 +0,0 @@
|
||||
angular.module('Darkswarm').directive "ofnInlineAlert", ->
|
||||
restrict: 'A'
|
||||
scope: true
|
||||
link: (scope, elem, attrs) ->
|
||||
scope.visible = true
|
||||
scope.close = ->
|
||||
scope.visible = false
|
||||
@@ -1,21 +0,0 @@
|
||||
angular.module('Darkswarm').directive "ofnPageAlert", ($timeout) ->
|
||||
restrict: 'A'
|
||||
scope: true
|
||||
link: (scope, elem, attrs) ->
|
||||
moveSelectors = [".off-canvas-wrap .inner-wrap",
|
||||
".off-canvas-wrap .inner-wrap .fixed",
|
||||
".off-canvas-fixed .top-bar",
|
||||
".off-canvas-fixed ofn-flash",
|
||||
".off-canvas-fixed nav.tab-bar",
|
||||
".off-canvas-fixed .page-alert"]
|
||||
|
||||
container_elems = $(moveSelectors.join(", "))
|
||||
|
||||
# Wait a moment after page load before showing the alert. Otherwise we often miss the
|
||||
# start of the animation.
|
||||
$timeout ->
|
||||
container_elems.addClass("move-up")
|
||||
, 1000
|
||||
|
||||
scope.close = ->
|
||||
container_elems.removeClass("move-up")
|
||||
@@ -1,10 +0,0 @@
|
||||
#new-tag-rule-dialog
|
||||
.text-normal.margin-bottom-30.text-center
|
||||
{{ 'js.admin.new_tag_rule_dialog.select_rule_type' | t }}
|
||||
|
||||
.text-center.margin-bottom-30
|
||||
-# %select.fullwidth{ 'select2-min-search' => 5, 'ng-model' => 'newRuleType', 'ng-options' => 'ruleType.id as ruleType.name for ruleType in availableRuleTypes' }
|
||||
%input.ofn-select2.fullwidth{ id: 'rule_type_selector', data: "ruleTypes", "min-search": "5", "ng-model": "ruleType" }
|
||||
|
||||
.text-center
|
||||
%input.button.red.icon-plus{ type: 'button', value: "{{ 'js.admin.new_tag_rule_dialog.add_rule' | t }}", "ng-click": 'addRule(tagGroup, ruleType)' }
|
||||
@@ -3,4 +3,4 @@
|
||||
%span.text-normal
|
||||
{{ 'admin.tags' | t }}
|
||||
%br
|
||||
%tags-with-translation.fullwidth{ object: 'object' }
|
||||
%tags-with-translation.fullwidth{ object: 'object', form: 'order_cycle_form', id: 'tags_with_translation'}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
%div
|
||||
%input{ type: "number", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_preferred_flat_percent", min: -100, max: 100, "invert-number": true, "ng-model": "rule.calculator.preferred_flat_percent" }
|
||||
%span.text-normal %
|
||||
@@ -1,5 +0,0 @@
|
||||
%div
|
||||
%input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_order_cycles_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_order_cycles_visibility]", data: 'visibilityOptions', "min-search": 5, "ng-model": "rule.preferred_matched_order_cycles_visibility", "ng-if": "!rule.is_default" }
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_order_cycles_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_order_cycles_visibility]", "ng-value": "'hidden'", "ng-if": "rule.is_default" }
|
||||
%span.text-normal{ "ng-if": "rule.is_default" }
|
||||
=t(:not_visible)
|
||||
@@ -1,5 +0,0 @@
|
||||
%div
|
||||
%input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_payment_methods_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_payment_methods_visibility]", data: 'visibilityOptions', "min-search": 5, "ng-model": "rule.preferred_matched_payment_methods_visibility", "ng-if": "!rule.is_default" }
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_payment_methods_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_payment_methods_visibility]", "ng-value": "'hidden'", "ng-if": "rule.is_default" }
|
||||
%span.text-normal{ "ng-if": "rule.is_default" }
|
||||
= t(:not_visible)
|
||||
@@ -1,5 +0,0 @@
|
||||
%div
|
||||
%input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_variants_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_variants_visibility]", data: 'visibilityOptions', "min-search": 5, "ng-model": "rule.preferred_matched_variants_visibility", "ng-if": "!rule.is_default" }
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_variants_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_variants_visibility]", "ng-value": "'hidden'", "ng-if": "rule.is_default" }
|
||||
%span.text-normal{ "ng-if": "rule.is_default" }
|
||||
= t(:not_visible)
|
||||
@@ -1,6 +0,0 @@
|
||||
%div
|
||||
%input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]", data: 'visibilityOptions', "min-search": 5, "ng-model": "rule.preferred_matched_shipping_methods_visibility", "ng-if": "!rule.is_default" }
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]", "ng-value": "'hidden'", "ng-if": "rule.is_default" }
|
||||
%span.text-normal{ "ng-if": "rule.is_default" }
|
||||
= t(:not_visible)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
%div{ id: "tr_{{tagGroup.startIndex + $index}}" }
|
||||
%table
|
||||
%colgroup
|
||||
%col.text{ width: "35%" }
|
||||
%col.inputs{ width: "55%" }
|
||||
%col.actions{ width: "10%" }
|
||||
%tr
|
||||
%td
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]", "ng-value": "rule.id" }
|
||||
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]", "ng-value": "rule.type" }
|
||||
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_priority", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][priority]", "ng-value": "tagGroup.startIndex + $index" }
|
||||
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_is_default", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][is_default]", "ng-value": "rule.is_default" }
|
||||
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]", "ng-value": "rule.preferred_customer_tags" }
|
||||
|
||||
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_{{opt[rule.type].taggable}}_tags", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_{{opt[rule.type].taggable}}_tags]", "ng-value": "opt[rule.type].tagListFor(rule)" }
|
||||
|
||||
%span.text-normal {{ opt[rule.type].textTop }}
|
||||
%td
|
||||
%tags-with-translation{ object: "rule", max: 1, "tags-attr" => "{{opt[rule.type].tagsAttr}}", "tag-list-attr" => "{{opt[rule.type].tagListAttr}}" }
|
||||
%td.actions{ rowspan: 2 }
|
||||
%a{ class: "delete-tag-rule icon-trash no-text", "ng-click": "deleteTagRule(tagGroup || defaultTagGroup, rule)" }
|
||||
%tr
|
||||
%td
|
||||
%span.text-normal {{ opt[rule.type].textBottom }}
|
||||
%td
|
||||
%div{ "ng-include": "opt[rule.type].inputTemplate" }
|
||||
|
||||
%hr
|
||||
@@ -12,7 +12,7 @@
|
||||
.row
|
||||
.columns.small-12
|
||||
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
|
||||
"ng-href" => "{{::hub.path}}#/shop_panel", "ofn-empties-cart" => "hub",
|
||||
"ng-href" => "{{::hub.path}}#/shop_panel",
|
||||
"ng-class" => "::{primary: hub.active, secondary: !hub.active}",
|
||||
"ng-click" => "$close()",
|
||||
"ofn-change-hub" => "hub"}
|
||||
|
||||
21
app/components/add_tag_rule_modal_component.rb
Normal file
21
app/components/add_tag_rule_modal_component.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddTagRuleModalComponent < ModalComponent
|
||||
def initialize(id:, tag_rule_types:, current_index:, div_id:, is_default: false,
|
||||
customer_tag: "", hidden_field_customer_tag_options: {} )
|
||||
super
|
||||
|
||||
@close_button = false
|
||||
@modal_class = "tiny"
|
||||
|
||||
@tag_rule_types = tag_rule_types
|
||||
@current_index = current_index
|
||||
@div_id = div_id
|
||||
@is_default = is_default
|
||||
@customer_tag = customer_tag
|
||||
@hidden_field_customer_tag_options = hidden_field_customer_tag_options
|
||||
end
|
||||
|
||||
attr_reader :tag_rule_types, :current_index, :div_id, :is_default, :customer_tag,
|
||||
:hidden_field_customer_tag_options
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
-# as far as I can tell we can't pass content to the parent template while rendering ie: something like :
|
||||
-# = render_parent do
|
||||
-# .something
|
||||
-# my content
|
||||
-# Workarount is to copy the ModalComponent template
|
||||
%div{ id: @id, "data-controller": @data_controller, "data-action": @data_action, "data-modal-instant-value": @instant, **@options }
|
||||
.reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" }
|
||||
.reveal-modal.fade.modal-component{ "data-modal-target": "modal", class: @modal_class }
|
||||
#new-tag-rule-dialog{ "data-controller": "add-tag-rule-modal",
|
||||
"data-add-tag-rule-modal-index-value": current_index }
|
||||
|
||||
-# Ideally we would use event to communicate the update of customer tag, but we would need
|
||||
-# the element with "data-controller": "add-tag-rule-modal"
|
||||
-# to be parent of the element with "data-controller": "tag-rule-group-form"
|
||||
-# so it could respond to event generated by "tag-rule-group-form".
|
||||
-# Here we are in the opposite situation so we use a hidden field to store the value of
|
||||
-# the customer tag, so it can be updated by "tag-rule-group-form"
|
||||
= hidden_field_tag "customer_tag", customer_tag, { "data-add-tag-rule-modal-target": "ruleCustomerTag" }.merge(hidden_field_customer_tag_options)
|
||||
.text-normal.margin-bottom-30.text-center
|
||||
= t('components.add_tag_rule_modal.select_rule_type')
|
||||
.text-center.margin-bottom-30
|
||||
= select_tag :rule_type_selector, options_for_select(tag_rule_types), { "data-controller": "tom-select", "data-add-tag-rule-modal-target": "rule", class: "primary no-search" }
|
||||
.text-center
|
||||
%input.button.red.icon-plus{ type: 'button',
|
||||
value: "#{t('components.add_tag_rule_modal.add_rule')}",
|
||||
"data-action": "click->add-tag-rule-modal#add click->modal#close",
|
||||
"data-add-tag-rule-modal-div-id-param": div_id,
|
||||
"data-add-tag-rule-modal-is-default-param": "#{is_default}"}
|
||||
|
||||
- if close_button?
|
||||
.text-center
|
||||
%input{ class: "button icon-plus #{close_button_class}", type: 'button', value: t('js.admin.modals.close'), "data-action": "click->modal#close" }
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Controller } from "stimulus";
|
||||
import showHttpError from "../../webpacker/js/services/show_http_error";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["rule", "ruleCustomerTag"];
|
||||
static values = { index: Number };
|
||||
|
||||
add({ params }) {
|
||||
const rule_type = this.ruleTarget.value;
|
||||
const index = this.indexValue;
|
||||
const divId = params["divId"];
|
||||
const isDefault = params["isDefault"];
|
||||
const customerTags = this.hasRuleCustomerTagTarget
|
||||
? this.ruleCustomerTagTarget.value
|
||||
: undefined;
|
||||
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.append("rule_type", rule_type);
|
||||
urlParams.append("index", index);
|
||||
urlParams.append("div_id", divId);
|
||||
urlParams.append("is_default", isDefault);
|
||||
if (customerTags != undefined) {
|
||||
urlParams.append("customer_tags", customerTags);
|
||||
}
|
||||
|
||||
// fetch from backend
|
||||
fetch(`tag_rules/new?${urlParams}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "text/vnd.turbo-stream.html",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
showHttpError(response.status);
|
||||
throw response;
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then((html) => {
|
||||
Turbo.renderStreamMessage(html);
|
||||
this.indexValue = parseInt(index) + 1;
|
||||
})
|
||||
.catch((error) => console.error(error));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// This controller will be called "example-component--example", ie "component-subdirectory--js-file-name"
|
||||
// This controller will be called "example", ie "js-file-name" minus the "_controller.js"
|
||||
// see controller/index.js for more info
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {}
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TagListInputComponent < ViewComponent::Base
|
||||
# method in a "hidden_field" form helper and is the method used to get a list of tag on the model
|
||||
def initialize(form:, method:, tags:,
|
||||
def initialize(name:, tags:,
|
||||
placeholder: I18n.t("components.tag_list_input.default_placeholder"),
|
||||
aria_label: nil)
|
||||
@f = form
|
||||
@method = method
|
||||
only_one: false,
|
||||
aria_label: nil,
|
||||
hidden_field_data_options: {})
|
||||
@name = name
|
||||
@tags = tags
|
||||
@placeholder = placeholder
|
||||
@only_one = only_one
|
||||
@aria_label_option = aria_label ? { 'aria-label': aria_label } : {}
|
||||
@hidden_field_data_options = hidden_field_data_options
|
||||
end
|
||||
|
||||
attr_reader :f, :method, :tags, :placeholder, :aria_label_option
|
||||
attr_reader :name, :tags, :placeholder, :only_one, :aria_label_option, :hidden_field_data_options
|
||||
|
||||
private
|
||||
|
||||
def display
|
||||
return "none" if tags.length >= 1 && only_one == true
|
||||
|
||||
"block"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
%div{ "data-controller": "tag-list-input-component--tag-list-input" }
|
||||
%div{ "data-controller": "tag-list-input", "data-tag-list-input-only-one-value": "#{only_one}" }
|
||||
.tags-input
|
||||
.tags
|
||||
- # We use display:none instead of hidden field, so changes to the value can be picked up by the bulkFormController
|
||||
= f.text_field method.to_sym, value: tags.join(","), "data-tag-list-input-component--tag-list-input-target": "tagList", "style": "display: none"
|
||||
%ul.tag-list{"data-tag-list-input-component--tag-list-input-target": "list"}
|
||||
%template{"data-tag-list-input-component--tag-list-input-target": "template"}
|
||||
= text_field_tag name, tags.join(","), {"data-tag-list-input-target": "tagList", "style": "display: none"}.merge(hidden_field_data_options)
|
||||
%ul.tag-list{"data-tag-list-input-target": "list"}
|
||||
%template{"data-tag-list-input-target": "template"}
|
||||
%li.tag-item
|
||||
.tag-template
|
||||
%span
|
||||
%a.remove-button{ "data-action": "click->tag-list-input-component--tag-list-input#removeTag" }
|
||||
%a.remove-button{ "data-action": "click->tag-list-input#removeTag" }
|
||||
×
|
||||
- tags.each do |tag|
|
||||
%li.tag-item
|
||||
.tag-template
|
||||
%span=tag
|
||||
%a.remove-button{ "data-action": "click->tag-list-input-component--tag-list-input#removeTag" }
|
||||
%a.remove-button{ "data-action": "click->tag-list-input#removeTag" }
|
||||
×
|
||||
= text_field_tag "variant_add_tag_#{f.object.id}".to_sym, nil, class: "input", placeholder: placeholder, "data-action": "keydown.enter->tag-list-input-component--tag-list-input#addTag keyup->tag-list-input-component--tag-list-input#filterInput", "data-tag-list-input-component--tag-list-input-target": "newTag", **aria_label_option
|
||||
= text_field_tag "variant_add_tag", nil, class: "input", placeholder: placeholder, "data-action": "keydown.enter->tag-list-input#addTag keyup->tag-list-input#filterInput blur->tag-list-input#addTag", "data-tag-list-input-target": "newTag", **aria_label_option, style: "display: #{display};"
|
||||
|
||||
@@ -2,17 +2,18 @@ import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["tagList", "newTag", "template", "list"];
|
||||
static values = { onlyOne: Boolean };
|
||||
|
||||
addTag(event) {
|
||||
// prevent hotkey form submitting the form (default action for "enter" key)
|
||||
event.preventDefault();
|
||||
|
||||
// Check if tag already exist
|
||||
const newTagName = this.newTagTarget.value.trim();
|
||||
const newTagName = this.newTagTarget.value.trim().replaceAll(" ", "-");
|
||||
if (newTagName.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if tag already exist
|
||||
const tags = this.tagListTarget.value.split(",");
|
||||
const index = tags.indexOf(newTagName);
|
||||
if (index != -1) {
|
||||
@@ -22,7 +23,13 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
// add to tagList
|
||||
this.tagListTarget.value = this.tagListTarget.value.concat(`,${newTagName}`);
|
||||
if (this.tagListTarget.value == "") {
|
||||
this.tagListTarget.value = newTagName;
|
||||
} else {
|
||||
this.tagListTarget.value = this.tagListTarget.value.concat(`,${newTagName}`);
|
||||
}
|
||||
// manualy dispatch an Input event so the change can get picked up by other controllers
|
||||
this.tagListTarget.dispatchEvent(new InputEvent("input"));
|
||||
|
||||
// Create new li component with value
|
||||
const newTagElement = this.templateTarget.content.cloneNode(true);
|
||||
@@ -32,6 +39,11 @@ export default class extends Controller {
|
||||
|
||||
// Clear new tag value
|
||||
this.newTagTarget.value = "";
|
||||
|
||||
// hide tag input if limited to one tag
|
||||
if (this.tagListTarget.value.split(",").length == 1 && this.onlyOneValue == true) {
|
||||
this.newTagTarget.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
removeTag(event) {
|
||||
@@ -40,13 +52,18 @@ export default class extends Controller {
|
||||
|
||||
// Remove tag from list
|
||||
const tags = this.tagListTarget.value.split(",");
|
||||
this.tagListTarget.value = tags.filter(tag => tag != tagName).join(",");
|
||||
this.tagListTarget.value = tags.filter((tag) => tag != tagName).join(",");
|
||||
|
||||
// manualy dispatch an Input event so the change gets picked up by the bulk form controller
|
||||
this.tagListTarget.dispatchEvent(new InputEvent("input"));
|
||||
|
||||
// Remove HTML element from the list
|
||||
event.srcElement.parentElement.parentElement.remove();
|
||||
|
||||
// Make sure the tag input is displayed
|
||||
if (this.tagListTarget.value.length == 0) {
|
||||
this.newTagTarget.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
filterInput(event) {
|
||||
|
||||
59
app/components/tag_rule_form_component.rb
Normal file
59
app/components/tag_rule_form_component.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TagRuleFormComponent < ViewComponent::Base
|
||||
def initialize(rule:, index:, customer_tags: "",
|
||||
hidden_field_customer_tag_options: {})
|
||||
@rule = rule
|
||||
@index = index
|
||||
@customer_tags = customer_tags
|
||||
@hidden_field_customer_tag_options = hidden_field_customer_tag_options
|
||||
end
|
||||
|
||||
attr_reader :rule, :index, :customer_tags, :hidden_field_customer_tag_options
|
||||
|
||||
private
|
||||
|
||||
def element_name(name)
|
||||
"enterprise[tag_rules_attributes][#{index}][#{name}]"
|
||||
end
|
||||
|
||||
def rule_data # rubocop:disable Metrics/MethodLength
|
||||
case rule.type
|
||||
when "TagRule::FilterShippingMethods"
|
||||
{
|
||||
text_top: t('components.tag_rule_form.tag_rules.shipping_method_tagged_top'),
|
||||
text_bottom: t('components.tag_rule_form.tag_rules.shipping_method_tagged_bottom'),
|
||||
taggable: "shipping_method",
|
||||
visibility_field: "preferred_matched_shipping_methods_visibility",
|
||||
}
|
||||
when "TagRule::FilterPaymentMethods"
|
||||
{
|
||||
text_top: t('components.tag_rule_form.tag_rules.payment_method_tagged_top'),
|
||||
text_bottom: t('components.tag_rule_form.tag_rules.payment_method_tagged_bottom'),
|
||||
taggable: "payment_method",
|
||||
visibility_field: "preferred_matched_payment_methods_visibility",
|
||||
}
|
||||
when "TagRule::FilterOrderCycles"
|
||||
{
|
||||
text_top: t('components.tag_rule_form.tag_rules.order_cycle_tagged_top'),
|
||||
text_bottom: t('components.tag_rule_form.tag_rules.order_cycle_tagged_bottom'),
|
||||
taggable: "exchange",
|
||||
visibility_field: "preferred_matched_order_cycles_visibility",
|
||||
}
|
||||
when "TagRule::FilterProducts"
|
||||
{
|
||||
text_top: t('components.tag_rule_form.tag_rules.inventory_tagged_top'),
|
||||
text_bottom: t('components.tag_rule_form.tag_rules.inventory_tagged_bottom'),
|
||||
taggable: "variant",
|
||||
visibility_field: "preferred_matched_variants_visibility",
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def visibility_options
|
||||
[
|
||||
[t('components.tag_rule_form.tag_rules.visible'), "visible"],
|
||||
[t('components.tag_rule_form.tag_rules.not_visible'), "hidden"]
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
%div{ id: "tr_#{index}" }
|
||||
%table
|
||||
%colgroup
|
||||
%col.text{ width: "35%" }
|
||||
%col.inputs{ width: "55%" }
|
||||
%col.actions{ width: "10%" }
|
||||
%tr
|
||||
%td
|
||||
= hidden_field_tag element_name("id"), rule.id
|
||||
= hidden_field_tag element_name("type"), rule.type
|
||||
= hidden_field_tag element_name("priority"), index
|
||||
= hidden_field_tag element_name("is_default"), rule.is_default
|
||||
= hidden_field_tag element_name("preferred_customer_tags"), customer_tags, hidden_field_customer_tag_options
|
||||
%span.text-normal
|
||||
= rule_data[:text_top]
|
||||
%td
|
||||
= render TagListInputComponent.new(name: element_name("preferred_#{rule_data[:taggable]}_tags"), tags: rule.tags.split(","), only_one: true)
|
||||
%td.actions{ rowspan: 2 , "data-controller": "delete-tag-rule", "data-delete-tag-rule-index-value": index }
|
||||
- if rule.new_record?
|
||||
= link_to("", "#", { "data-action": "click->delete-tag-rule#delete" ,class: "delete-tag-rule icon-trash no-text"})
|
||||
- else
|
||||
= link_to("", "#{admin_enterprise_tag_rule_url(rule.enterprise_id, rule.id)}?index=#{index}", { "data-turbo-method": "delete", "data-turbo-confirm": t("admin.tag_rules.confirm_delete"), class: "delete-tag-rule icon-trash no-text" })
|
||||
%tr
|
||||
%td
|
||||
%span.text-normal
|
||||
= rule_data[:text_bottom]
|
||||
%td
|
||||
%div
|
||||
%div
|
||||
- if rule.is_default
|
||||
= hidden_field_tag "enterprise[tag_rules_attributes][#{index}][#{rule_data[:visibility_field]}]", "hidden"
|
||||
%span.text-normal
|
||||
= t(:not_visible)
|
||||
- else
|
||||
= select_tag "enterprise[tag_rules_attributes][#{index}][#{rule_data[:visibility_field]}]", options_for_select(visibility_options, rule.public_send(rule_data[:visibility_field].to_sym) ), { "data-controller": "tom-select", class: "primary no-search" }
|
||||
26
app/components/tag_rule_group_form_component.rb
Normal file
26
app/components/tag_rule_group_form_component.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TagRuleGroupFormComponent < ViewComponent::Base
|
||||
def initialize(group:, index:, customer_rule_index:, tag_rule_types:)
|
||||
@group = group
|
||||
@index = index
|
||||
@customer_rule_index = customer_rule_index
|
||||
@tag_rule_types = tag_rule_types
|
||||
end
|
||||
|
||||
attr_reader :group, :index, :customer_rule_index, :tag_rule_types
|
||||
|
||||
private
|
||||
|
||||
def form_id
|
||||
"tg_#{index}"
|
||||
end
|
||||
|
||||
def customer_tag_rule_div_id
|
||||
"new-customer-tag-rule-#{index}"
|
||||
end
|
||||
|
||||
def tag_list_input_name
|
||||
"group[#{index}][preferred_customer_tags]"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
%div{ id: form_id, "data-controller": "tag-rule-group-form" }
|
||||
- rule_index = customer_rule_index
|
||||
.customer_tag
|
||||
.header
|
||||
%table
|
||||
%colgroup
|
||||
%col{width: '35%'}
|
||||
%col{width: '65%'}
|
||||
%tr
|
||||
%td
|
||||
%h5
|
||||
= t('components.tag_rule_group_form.for_customers_tagged')
|
||||
%td
|
||||
= render(TagListInputComponent.new(name: tag_list_input_name,
|
||||
tags: group[:tags],
|
||||
only_one: true,
|
||||
hidden_field_data_options: { "data-action": "input->tag-rule-group-form#updatePreferredCustomerTag", "data-tag-rule-group-form-target": "customerTag" }))
|
||||
%div{ id: customer_tag_rule_div_id }
|
||||
- if group[:rules].empty?
|
||||
.no_rules
|
||||
= t('components.tag_rule_group_form.no_rules_yet')
|
||||
- else
|
||||
- group[:rules].each do |rule|
|
||||
- rule_index += 1
|
||||
= render(TagRuleFormComponent.new(rule: rule,
|
||||
index: rule_index,
|
||||
customer_tags: group[:tags],
|
||||
hidden_field_customer_tag_options: { "data-tag-rule-group-form-target": "ruleCustomerTag" }))
|
||||
|
||||
%hr
|
||||
.add_rule.text-center
|
||||
%input.button{ type: 'button', value: t('components.tag_rule_group_form.add_new_rule'), "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "tag_rule_add_new_rule_#{index}" }
|
||||
|
||||
= render AddTagRuleModalComponent.new(id: "tag_rule_add_new_rule_#{index}",
|
||||
tag_rule_types: tag_rule_types,
|
||||
current_index: (rule_index + 1),
|
||||
div_id: customer_tag_rule_div_id,
|
||||
customer_tag: group[:tags],
|
||||
hidden_field_customer_tag_options: { "data-tag-rule-group-form-target": "ruleCustomerTag" })
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["customerTag", "ruleCustomerTag"];
|
||||
|
||||
updatePreferredCustomerTag() {
|
||||
const customerTag = this.customerTagTarget.value;
|
||||
|
||||
this.ruleCustomerTagTargets.forEach((element) => (element.value = customerTag));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module VerticalEllipsisMenu
|
||||
class Component < ViewComponent::Base
|
||||
end
|
||||
end
|
||||
4
app/components/vertical_ellipsis_menu_component.rb
Normal file
4
app/components/vertical_ellipsis_menu_component.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class VerticalEllipsisMenuComponent < ViewComponent::Base
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
.vertical-ellipsis-menu{ "data-controller": "vertical-ellipsis-menu--component" }
|
||||
%i.fa.fa-ellipsis-v{ "data-action": "click->vertical-ellipsis-menu--component#toggle" }
|
||||
.vertical-ellipsis-menu-content{ "data-vertical-ellipsis-menu--component-target": "content" }
|
||||
.vertical-ellipsis-menu{ "data-controller": "vertical-ellipsis-menu" }
|
||||
%i.fa.fa-ellipsis-v{ "data-action": "click->vertical-ellipsis-menu#toggle" }
|
||||
.vertical-ellipsis-menu-content{ "data-vertical-ellipsis-menu-target": "content" }
|
||||
= content
|
||||
@@ -83,6 +83,7 @@ module Admin
|
||||
format.turbo_stream
|
||||
end
|
||||
else
|
||||
load_tag_rule_types
|
||||
respond_with(@object) do |format|
|
||||
format.json {
|
||||
render json: { errors: @object.errors.messages }, status: :unprocessable_entity
|
||||
@@ -147,6 +148,18 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def new_tag_rule_group
|
||||
load_tag_rule_types
|
||||
|
||||
@index = params[:index]
|
||||
@customer_rule_index = params[:customer_rule_index].to_i
|
||||
@group = { tags: [], rules: [] }
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def delete_custom_tab
|
||||
@@ -379,16 +392,15 @@ module Admin
|
||||
end
|
||||
|
||||
def load_tag_rule_types
|
||||
# Load rule types
|
||||
@tag_rule_types = [
|
||||
{ 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') }
|
||||
[t(".form.tag_rules.show_hide_shipping"), "FilterShippingMethods"],
|
||||
[t(".form.tag_rules.show_hide_payment"), "FilterPaymentMethods"],
|
||||
[t(".form.tag_rules.show_hide_order_cycles"), "FilterOrderCycles"]
|
||||
]
|
||||
|
||||
return unless helpers.feature?(:inventory, @object)
|
||||
|
||||
@tag_rule_types.prepend({ id: "FilterProducts", name: t('js.tag_rules.show_hide_variants') })
|
||||
@tag_rule_types.prepend([t(".form.tag_rules.show_hide_variants"), "FilterProducts"])
|
||||
end
|
||||
|
||||
def setup_property
|
||||
|
||||
@@ -49,7 +49,7 @@ module Admin
|
||||
errors: @importer.errors.full_messages
|
||||
}
|
||||
|
||||
if helpers.feature?(:inventory, spree_current_user.enterprises)
|
||||
if helpers.feature?(:inventory, *spree_current_user.enterprises)
|
||||
json[:results][:inventory_created] = @importer.inventory_created_count
|
||||
json[:results][:inventory_updated] = @importer.inventory_updated_count
|
||||
end
|
||||
@@ -175,7 +175,7 @@ module Admin
|
||||
|
||||
# Return an error if trying to import into inventories when inventory is disable
|
||||
def can_import_into_inventories?
|
||||
return true if helpers.feature?(:inventory, spree_current_user.enterprises) ||
|
||||
return true if helpers.feature?(:inventory, *spree_current_user.enterprises) ||
|
||||
params.dig(:settings, "import_into") != 'inventories'
|
||||
|
||||
redirect_to admin_product_import_url, notice: I18n.t(:product_import_inventory_disable)
|
||||
|
||||
@@ -123,6 +123,13 @@ module Admin
|
||||
@page = params[:page].presence || 1
|
||||
@per_page = params[:per_page].presence || 15
|
||||
@q = params.permit(q: {})[:q] || { s: 'name asc' }
|
||||
|
||||
# Transform on_hand sorting to include backorderable_priority (on-demand) for proper ordering
|
||||
if @q[:s] == 'on_hand asc'
|
||||
@q[:s] = ['backorderable_priority asc', @q[:s]]
|
||||
elsif @q[:s] == 'on_hand desc'
|
||||
@q[:s] = ['backorderable_priority desc', @q[:s]]
|
||||
end
|
||||
end
|
||||
|
||||
def producers
|
||||
@@ -155,8 +162,27 @@ module Admin
|
||||
product_query = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
.editable_products.merge(product_scope_with_includes).ransack(ransack_query).result
|
||||
|
||||
@pagy, @products = pagy(product_query.order(:name), limit: @per_page, page: @page,
|
||||
size: [1, 2, 2, 1])
|
||||
# Postgres requires ORDER BY expressions to appear in the SELECT list when using DISTINCT.
|
||||
# When the current ransack sort uses the computed stock columns, include them in the select
|
||||
# so the generated COUNT/DISTINCT query is valid.
|
||||
sort_columns = Array(@q && @q[:s]).flatten
|
||||
if sort_columns.any? { |s|
|
||||
s.to_s.include?('on_hand') || s.to_s.include?('backorderable_priority')
|
||||
}
|
||||
|
||||
product_query = product_query.select(
|
||||
Arel.sql('spree_products.*'),
|
||||
Spree::Product.backorderable_priority_sql,
|
||||
Spree::Product.on_hand_sql
|
||||
)
|
||||
end
|
||||
|
||||
@pagy, @products = pagy(
|
||||
product_query.order(:name),
|
||||
limit: @per_page,
|
||||
page: @page,
|
||||
size: [1, 2, 2, 1]
|
||||
)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
|
||||
@@ -22,14 +22,12 @@ module Admin
|
||||
def show
|
||||
@report = report_class.new(spree_current_user, params, render: false)
|
||||
@rendering_options = rendering_options
|
||||
|
||||
show_report
|
||||
end
|
||||
|
||||
def create
|
||||
@report = report_class.new(spree_current_user, params, render: true)
|
||||
update_rendering_options
|
||||
|
||||
render_in_background
|
||||
end
|
||||
|
||||
@@ -61,7 +59,9 @@ module Admin
|
||||
@blob = ReportBlob.create_for_upload_later!(report_filename)
|
||||
|
||||
ReportJob.perform_later(
|
||||
report_class:, user: spree_current_user, params:,
|
||||
report_class:,
|
||||
user: spree_current_user,
|
||||
params:,
|
||||
format: report_format,
|
||||
blob: @blob,
|
||||
channel: ScopedChannel.for_id(params[:uuid]),
|
||||
|
||||
@@ -1,12 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class TagRulesController < Admin::ResourceController
|
||||
class TagRulesController < Spree::Admin::BaseController
|
||||
respond_to :json
|
||||
|
||||
respond_override destroy: { json: {
|
||||
success: lambda { head :no_content }
|
||||
} }
|
||||
def new
|
||||
@index = params[:index]
|
||||
@div_id = params[:div_id]
|
||||
is_default = params[:is_default]
|
||||
@customer_tags = params[:customer_tags]
|
||||
|
||||
status = :ok
|
||||
if permitted_tag_rule_type.include?(params[:rule_type])
|
||||
@default_rule = "TagRule::#{params[:rule_type]}".constantize.new(is_default:)
|
||||
else
|
||||
flash.now[:error] = t(".not_supported_type")
|
||||
status = :internal_server_error
|
||||
end
|
||||
|
||||
respond_with do |format|
|
||||
format.turbo_stream { render :new, status: }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@rule = TagRule.find(params[:id])
|
||||
@index = params[:index]
|
||||
authorize! :destroy, @rule
|
||||
|
||||
status = :ok
|
||||
if @rule.destroy
|
||||
flash[:success] = Spree.t(:successfully_removed, resource: "Tag Rule")
|
||||
else
|
||||
flash.now[:error] = t(".destroy_error")
|
||||
status = :internal_server_error
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream { render :destroy, status: }
|
||||
end
|
||||
end
|
||||
|
||||
def map_by_tag
|
||||
respond_to do |format|
|
||||
@@ -39,5 +72,13 @@ module Admin
|
||||
Enterprise.managed_by(spree_current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def model_class
|
||||
TagRule
|
||||
end
|
||||
|
||||
def permitted_tag_rule_type
|
||||
%w{FilterOrderCycles FilterPaymentMethods FilterProducts FilterShippingMethods}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -84,6 +84,7 @@ module ReportsActions
|
||||
else
|
||||
params[:fields_to_show]
|
||||
end,
|
||||
display_metadata_rows: false,
|
||||
display_summary_row: request.get?,
|
||||
display_header_row: false
|
||||
}
|
||||
@@ -94,6 +95,7 @@ module ReportsActions
|
||||
rendering_options.update(
|
||||
options: {
|
||||
fields_to_show: params[:fields_to_show],
|
||||
display_metadata_rows: params[:display_metadata_rows].present?,
|
||||
display_summary_row: params[:display_summary_row].present?,
|
||||
display_header_row: params[:display_header_row].present?
|
||||
}
|
||||
|
||||
@@ -9,11 +9,7 @@ class PaymentsController < BaseController
|
||||
@payment = Spree::Payment.find(params[:id])
|
||||
authorize! :show, @payment.order
|
||||
|
||||
if (url = @payment.cvv_response_message)
|
||||
redirect_to url
|
||||
else
|
||||
redirect_to order_url(@payment.order)
|
||||
end
|
||||
redirect_to(@payment.redirect_auth_url || order_url(@payment.order))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
13
app/controllers/well_known_controller.rb
Normal file
13
app/controllers/well_known_controller.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class WellKnownController < ApplicationController
|
||||
layout nil
|
||||
|
||||
def dfc
|
||||
base = "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/scopes.rdf#"
|
||||
render json: {
|
||||
"#{base}ReadEnterprise" => "/api/dfc/enterprises/",
|
||||
"#{base}ReadProducts" => "/api/dfc/supplied_products/",
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
module EnterprisesHelper
|
||||
module EnterprisesHelper # rubocop:disable Metrics/ModuleLength
|
||||
def add_check_if_single(count)
|
||||
if count == 1
|
||||
{ checked: true }
|
||||
@@ -28,7 +28,7 @@ module Admin
|
||||
show_connected_apps = can?(:manage_connected_apps, enterprise) &&
|
||||
(connected_apps_enabled(enterprise).present? ||
|
||||
dfc_platforms_available?)
|
||||
show_inventory_settings = feature?(:inventory, spree_current_user.enterprises) && is_shop
|
||||
show_inventory_settings = feature?(:inventory, *spree_current_user.enterprises) && is_shop
|
||||
|
||||
show_options = {
|
||||
show_properties:,
|
||||
@@ -50,7 +50,7 @@ module Admin
|
||||
end
|
||||
|
||||
def dfc_platforms_available?
|
||||
DfcProvider::PlatformsController::PLATFORM_IDS.keys.any? do |id|
|
||||
ApiUser::PLATFORMS.keys.any? do |id|
|
||||
feature?(id, spree_current_user)
|
||||
end
|
||||
end
|
||||
@@ -71,8 +71,35 @@ module Admin
|
||||
"#{enterprise_attachment_removal_panel}_panel"
|
||||
end
|
||||
|
||||
def enterprise_sells_options
|
||||
scope = "admin.enterprises.admin_index.sells_options"
|
||||
Enterprise::SELLS.map { |s| [I18n.t(s, scope:), s] }
|
||||
end
|
||||
|
||||
# Group tag rules per rule.preferred_customer_tags
|
||||
def tag_groups(tag_rules)
|
||||
tag_rules.each_with_object([]) do |tag_rule, tag_groups|
|
||||
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.split(","))
|
||||
|
||||
if tag_group[:rules].blank?
|
||||
tag_groups << tag_group
|
||||
tag_group[:position] = tag_groups.count
|
||||
end
|
||||
|
||||
tag_group[:rules] << tag_rule
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_match(tag_groups, tags)
|
||||
tag_groups.each do |tag_group|
|
||||
return tag_group if tag_group[:tags].length == tags.length &&
|
||||
(tag_group[:tags] & tags) == tag_group[:tags]
|
||||
end
|
||||
{ tags:, rules: [] }
|
||||
end
|
||||
|
||||
def build_enterprise_side_menu_items(is_shop:, show_options: ) # rubocop:disable Metrics/MethodLength
|
||||
[
|
||||
{ name: 'primary_details', icon_class: "icon-home", show: true, selected: 'selected' },
|
||||
|
||||
@@ -1,6 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module LinkHelper
|
||||
def link_to_or_disabled(name = nil, options = nil, html_options = nil, &block)
|
||||
html_options, options, name = options, name, block if block_given?
|
||||
html_options ||= {}
|
||||
|
||||
if !!html_options.delete(:disabled)
|
||||
# https://www.scottohara.me/blog/2021/05/28/disabled-links.html
|
||||
html_options.merge!(
|
||||
'aria-disabled': true,
|
||||
class: (html_options[:class].to_s.split + ["disabled"]).uniq.join(" "),
|
||||
role: "link"
|
||||
)
|
||||
if block_given?
|
||||
content_tag("a", name, **html_options, &block)
|
||||
else
|
||||
content_tag("a", name, **html_options)
|
||||
end
|
||||
elsif block_given?
|
||||
link_to options, html_options, &block
|
||||
else
|
||||
link_to name, options, html_options
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_service(baseurl, name, html_options = {}, &)
|
||||
return if name.blank?
|
||||
|
||||
|
||||
@@ -62,6 +62,12 @@ module ShopHelper
|
||||
true
|
||||
end
|
||||
|
||||
def shop_tab_class(tab)
|
||||
return unless (tab == "home" && show_home_tab?) || current_order(false)&.order_cycle.nil?
|
||||
|
||||
"with-darker-background"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def show_groups_tabs?
|
||||
|
||||
@@ -9,6 +9,7 @@ class ApplicationRecord < ActiveRecord::Base
|
||||
include ArelHelpers::JoinAssociation
|
||||
|
||||
self.abstract_class = true
|
||||
self.include_root_in_json = true
|
||||
|
||||
def self.image_service
|
||||
ENV["S3_BUCKET"].present? ? :amazon_public : :local
|
||||
|
||||
35
app/models/concerns/product_sort_by_stocks.rb
Normal file
35
app/models/concerns/product_sort_by_stocks.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ProductSortByStocks
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@on_hand_sql = Arel.sql("(
|
||||
SELECT COALESCE(SUM(si.count_on_hand), 0)
|
||||
FROM spree_variants v
|
||||
JOIN spree_stock_items si ON si.variant_id = v.id
|
||||
WHERE v.product_id = spree_products.id
|
||||
GROUP BY v.product_id
|
||||
)")
|
||||
|
||||
@backorderable_priority_sql = Arel.sql("(
|
||||
SELECT BOOL_OR(si.backorderable)
|
||||
FROM spree_variants v
|
||||
JOIN spree_stock_items si ON si.variant_id = v.id
|
||||
WHERE v.product_id = spree_products.id
|
||||
GROUP BY v.product_id
|
||||
)")
|
||||
|
||||
class << self
|
||||
attr_reader :on_hand_sql, :backorderable_priority_sql
|
||||
end
|
||||
|
||||
ransacker :on_hand do
|
||||
@on_hand_sql
|
||||
end
|
||||
|
||||
ransacker :backorderable_priority do
|
||||
@backorderable_priority_sql
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -75,6 +75,7 @@ class Enterprise < ApplicationRecord
|
||||
has_one :stripe_account, dependent: :destroy
|
||||
has_many :vouchers, dependent: :restrict_with_exception
|
||||
has_many :connected_apps, dependent: :destroy
|
||||
has_many :dfc_permissions, dependent: :destroy
|
||||
has_one :custom_tab, dependent: :destroy
|
||||
|
||||
delegate :latitude, :longitude, :city, :state_name, to: :address
|
||||
|
||||
@@ -193,7 +193,7 @@ module ProductImport
|
||||
order('is_primary_producer ASC, name').
|
||||
map { |e| @editable_enterprises[e.name] = e.id }
|
||||
|
||||
return unless OpenFoodNetwork::FeatureToggle.enabled?(:inventory, @current_user.enterprises)
|
||||
return unless OpenFoodNetwork::FeatureToggle.enabled?(:inventory, *@current_user.enterprises)
|
||||
|
||||
@inventory_permissions = permissions.variant_override_enterprises_per_hub
|
||||
end
|
||||
|
||||
@@ -142,6 +142,7 @@ module Spree
|
||||
|
||||
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty
|
||||
|
||||
can :new, TagRule
|
||||
can [:admin, :map_by_tag, :destroy], TagRule do |tag_rule|
|
||||
user.enterprises.include? tag_rule.enterprise
|
||||
end
|
||||
@@ -149,7 +150,7 @@ module Spree
|
||||
can [:admin, :index, :create], Enterprise
|
||||
can [:read, :edit, :update,
|
||||
:remove_logo, :remove_promo_image, :remove_terms_and_conditions,
|
||||
:bulk_update, :resend_confirmation], Enterprise do |enterprise|
|
||||
:bulk_update, :resend_confirmation, :new_tag_rule_group], Enterprise do |enterprise|
|
||||
OpenFoodNetwork::Permissions.new(user).editable_enterprises.include? enterprise
|
||||
end
|
||||
can [:welcome, :register], Enterprise do |enterprise|
|
||||
@@ -213,7 +214,7 @@ module Spree
|
||||
managed_product_enterprises.include? variant.supplier
|
||||
end
|
||||
|
||||
if OpenFoodNetwork::FeatureToggle.enabled?(:inventory, user.enterprises)
|
||||
if OpenFoodNetwork::FeatureToggle.enabled?(:inventory, *user.enterprises)
|
||||
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
|
||||
next false unless vo.hub.present? && vo.variant&.supplier.present?
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ module Spree
|
||||
scope :failed, -> { with_state('failed') }
|
||||
scope :valid, -> { where.not(state: %w(failed invalid)) }
|
||||
scope :void, -> { with_state('void') }
|
||||
scope :authorization_action_required, -> { where.not(cvv_response_message: nil) }
|
||||
scope :authorization_action_required, -> { where.not(redirect_auth_url: nil) }
|
||||
scope :requires_authorization, -> { with_state("requires_authorization") }
|
||||
scope :with_payment_intent, ->(code) { where(response_code: code) }
|
||||
|
||||
@@ -164,7 +164,7 @@ module Spree
|
||||
end
|
||||
|
||||
def clear_authorization_url
|
||||
update_attribute(:cvv_response_message, nil)
|
||||
update_attribute(:redirect_auth_url, nil)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -241,7 +241,8 @@ module Spree
|
||||
if response.cvv_result
|
||||
self.cvv_response_code = response.cvv_result['code']
|
||||
self.cvv_response_message = response.cvv_result['message']
|
||||
if cvv_response_message.present?
|
||||
self.redirect_auth_url = response.cvv_result['redirect_auth_url']
|
||||
if redirect_auth_url.present?
|
||||
return require_authorization!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,6 +19,7 @@ require 'open_food_network/property_merge'
|
||||
module Spree
|
||||
class Product < ApplicationRecord
|
||||
include LogDestroyPerformer
|
||||
include ProductSortByStocks
|
||||
|
||||
self.belongs_to_required_by_default = false
|
||||
# These columns have been moved to variant. Currently this is only for documentation purposes,
|
||||
@@ -30,7 +31,7 @@ module Spree
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
searchable_attributes :meta_keywords, :sku
|
||||
searchable_attributes :meta_keywords, :sku, :on_hand, :backorderable_priority
|
||||
searchable_associations :properties, :variants
|
||||
searchable_scopes :active, :with_properties
|
||||
|
||||
|
||||
@@ -19,4 +19,9 @@ class TagRule < ApplicationRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The following method must be overriden in a concrete tagRule
|
||||
def tags
|
||||
raise NotImplementedError, 'please use concrete TagRule'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,6 +14,10 @@ class TagRule::FilterOrderCycles < TagRule
|
||||
preferred_matched_order_cycles_visibility != "visible"
|
||||
end
|
||||
|
||||
def tags
|
||||
preferred_exchange_tags
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def exchange_for(order_cycle)
|
||||
|
||||
@@ -13,4 +13,8 @@ class TagRule::FilterPaymentMethods < TagRule
|
||||
def reject_matched?
|
||||
preferred_matched_payment_methods_visibility != "visible"
|
||||
end
|
||||
|
||||
def tags
|
||||
preferred_payment_method_tags
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,5 +18,9 @@ class TagRule
|
||||
def reject_matched?
|
||||
preferred_matched_variants_visibility != "visible"
|
||||
end
|
||||
|
||||
def tags
|
||||
preferred_variant_tags
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,4 +13,8 @@ class TagRule::FilterShippingMethods < TagRule
|
||||
preferred_tags = preferred_shipping_method_tags.split(",")
|
||||
shipping_method_tags.intersect?(preferred_tags)
|
||||
end
|
||||
|
||||
def tags
|
||||
preferred_shipping_method_tags
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,8 +10,8 @@ module Api
|
||||
:preferred_shopfront_taxon_order, :preferred_shopfront_producer_order,
|
||||
:preferred_shopfront_order_cycle_order, :show_customer_names_to_suppliers,
|
||||
:show_customer_contacts_to_suppliers,
|
||||
:preferred_shopfront_product_sorting_method, :owner, :contact, :users, :tag_groups,
|
||||
:default_tag_group, :require_login, :allow_guest_orders, :allow_order_changes,
|
||||
:preferred_shopfront_product_sorting_method, :owner, :contact, :users,
|
||||
:require_login, :allow_guest_orders, :allow_order_changes,
|
||||
:logo, :promo_image, :terms_and_conditions,
|
||||
:terms_and_conditions_file_name, :terms_and_conditions_updated_at,
|
||||
:preferred_invoice_order_by_supplier, :preferred_product_low_stock_display,
|
||||
@@ -50,41 +50,8 @@ module Api
|
||||
object.terms_and_conditions_blob&.created_at&.to_s
|
||||
end
|
||||
|
||||
def tag_groups
|
||||
prioritized_tag_rules.each_with_object([]) do |tag_rule, tag_groups|
|
||||
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.
|
||||
split(",").
|
||||
map{ |t| { text: t } })
|
||||
if tag_group[:rules].blank?
|
||||
tag_groups << tag_group
|
||||
tag_group[:position] = tag_groups.count
|
||||
end
|
||||
tag_group[:rules] << Api::Admin::TagRuleSerializer.new(tag_rule).serializable_hash
|
||||
end
|
||||
end
|
||||
|
||||
def default_tag_group
|
||||
default_rules = object.tag_rules.select(&:is_default)
|
||||
serialized_rules =
|
||||
ActiveModel::ArraySerializer.new(default_rules,
|
||||
each_serializer: Api::Admin::TagRuleSerializer)
|
||||
{ tags: [], rules: serialized_rules }
|
||||
end
|
||||
|
||||
def find_match(tag_groups, tags)
|
||||
tag_groups.each do |tag_group|
|
||||
return tag_group if tag_group[:tags].length == tags.length &&
|
||||
(tag_group[:tags] & tags) == tag_group[:tags]
|
||||
end
|
||||
{ tags:, rules: [] }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prioritized_tag_rules
|
||||
object.tag_rules.prioritised.reject(&:is_default)
|
||||
end
|
||||
|
||||
# Returns a hash of URLs for specified versions of an attachment.
|
||||
#
|
||||
# Example result:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module Api
|
||||
class PaymentSerializer < ActiveModel::Serializer
|
||||
attributes :amount, :updated_at, :payment_method, :state, :cvv_response_message
|
||||
attributes :amount, :updated_at, :payment_method, :state, :redirect_auth_url
|
||||
|
||||
def payment_method
|
||||
object.payment_method.try(:name)
|
||||
|
||||
@@ -40,7 +40,7 @@ module Checkout
|
||||
# Stripe::AuthorizeResponsePatcher patches the Stripe authorization response
|
||||
# so that this field stores the redirect URL. It also verifies that it is a Stripe URL.
|
||||
def stripe_payment_url(payment)
|
||||
payment.cvv_response_message
|
||||
payment.redirect_auth_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# /checkout; for admin payments and subscription payemnts it's the order url.
|
||||
#
|
||||
# This class confirms that the payment intent matches what's in our database,
|
||||
# marks the payment as complete, and removes the cvv_response_message field,
|
||||
# marks the payment as complete, and removes the redirect_auth_url field,
|
||||
# which we use to indicate that authorization is required. It also completes the
|
||||
# Order, if appropriate.
|
||||
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
# Tag rules exists in the context of enterprise, customer, and variant_overrides,
|
||||
# and are applied to variant_overrides only. Returns a Spree::Variant AR object.
|
||||
|
||||
# The filtering is somewhat not intuitive when they are conflicting rules in play:
|
||||
# * When a variant is hidden by a default rule, the order of customer related rules doesn't matter
|
||||
# ( despite the use of `TagRule::FilterProducts.prioritised` ). It will apply the "show rule"
|
||||
# if any
|
||||
# * When there is no default rule, the order of customer related rules doesn't matter, it will
|
||||
# apply the "hide rule" if any
|
||||
#
|
||||
class ProductTagRulesFilterer
|
||||
def initialize(distributor, customer, variants_relation)
|
||||
@distributor = distributor
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
= enterprise_form.check_box :is_primary_producer
|
||||
= t('.producer')
|
||||
- if spree_current_user.admin?
|
||||
%td= enterprise_form.select :sells, Enterprise::SELLS, {}, class: 'select2 fullwidth'
|
||||
%td= enterprise_form.select :sells, enterprise_sells_options, {}, class: 'select2 fullwidth'
|
||||
%td= enterprise_form.check_box :visible, {}, 'public', 'hidden'
|
||||
- if spree_current_user.admin?
|
||||
%td= enterprise_form.select :owner_id, enterprise.users.map{ |e| [ e.email, e.id ] }, {}, class: "select2 fullwidth"
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
= render partial: "admin/json/injection_ams", locals: { ngModule: "admin.tagRules", name: "ruleTypes", json: @tag_rule_types.to_json }
|
||||
|
||||
.row{ "ng-app" => "admin.tagRules", "ng-controller": "TagRulesCtrl" }
|
||||
.row
|
||||
.eleven.columns.alpha.omega
|
||||
%ofn-sortable{ axis: "y", handle: ".header", items: '.customer_tag', position: "tagGroup.position", "after-sort": "updateRuleCounts()" }
|
||||
.no_tags{ "ng-show": "tagGroups.length == 0" }
|
||||
= t('.no_tags_yet')
|
||||
= render 'admin/enterprises/form/tag_rules/default_rules'
|
||||
-# = render 'customer_tags'
|
||||
.customer_tag{ id: "tg_{{tagGroup.position}}", "ng-repeat": "tagGroup in tagGroups" }
|
||||
.header
|
||||
%table
|
||||
%colgroup
|
||||
%col{width: '35%'}
|
||||
%col{width: '65%'}
|
||||
%tr
|
||||
%td
|
||||
%h5
|
||||
= t('.for_customers_tagged')
|
||||
%td
|
||||
%tags-with-translation{ object: "tagGroup", max: 1, "on-tag-added": "updateTagsRulesFor(tagGroup)", "on-tag-removed": "updateTagsRulesFor(tagGroup)" }
|
||||
%div{ "data-turbo": true }
|
||||
- current_group_index = 0
|
||||
- # We use a high enough index increment so that the default tag rule should not overlap with the tag rules
|
||||
- # Rails will deal with non continous numbered tag_rules_attributes just fine, it saves us from having to manage the index state in javascript
|
||||
- current_rule_index = 1000
|
||||
- rules = @enterprise.tag_rules.prioritised.reject(&:is_default)
|
||||
- if rules.empty?
|
||||
.no_tags
|
||||
= t('.no_tags_yet')
|
||||
|
||||
.no_rules{ "ng-show": "tagGroup.rules.length == 0" }
|
||||
= t('.no_rules_yet')
|
||||
.tag_rule{ "ng-repeat": "rule in tagGroup.rules" }
|
||||
.add_rule.text-center
|
||||
%input.button.icon-plus{ type: 'button', value: t('.add_new_rule'), "add-new-rule-to" => "addNewRuleTo", "tag-group" => "tagGroup", "new-tag-rule-dialog" => true }
|
||||
.add_tag
|
||||
%input.button.red.icon-plus{ type: 'button', value: t('.add_new_tag'), "ng-click": 'addNewTag()' }
|
||||
= render 'admin/enterprises/form/tag_rules/default_rules', f:, current_rule_index:
|
||||
|
||||
#customer-tag-rule
|
||||
- tag_groups(rules).each_with_index do |group, group_index|
|
||||
- current_group_index = group_index + 1
|
||||
= render TagRuleGroupFormComponent.new(group:, index: group_index, customer_rule_index: current_rule_index, tag_rule_types: @tag_rule_types)
|
||||
- # Same as above, We use a high enough increcment so that the previous tag rule group does not overlap with the next tag rule group
|
||||
- current_rule_index += 1000
|
||||
|
||||
.add_tag{ "data-controller": "tag-rule-group" }
|
||||
= hidden_field_tag "group_index", current_group_index, { "data-tag-rule-group-target": "index" }
|
||||
= hidden_field_tag "customer_rule_index", current_rule_index, { "data-tag-rule-group-target": "customerRuleIndex" }
|
||||
%input.button{ type: 'button', value: t('.add_new_tag'), "data-action": "click->tag-rule-group#add" }
|
||||
|
||||
@@ -8,12 +8,23 @@
|
||||
%h5
|
||||
= t('.by_default')
|
||||
%i.text-big.icon-question-sign{ "data-controller": "help-modal-link", "data-action": "click->help-modal-link#open", "data-help-modal-link-target-value": "tag_rule_help_modal" }
|
||||
.no_rules{ "ng-show": "defaultTagGroup.rules.length == 0" }
|
||||
= t('.no_rules_yet')
|
||||
.tag_rule{ "ng-repeat": "rule in defaultTagGroup.rules" }
|
||||
.add_rule.text-center
|
||||
%input.button.icon-plus{ type: 'button', value: t('.add_new_button'), "add-new-rule-to" => "addNewRuleTo", "tag-group" => "defaultTagGroup", "new-tag-rule-dialog" => true }
|
||||
#default-tag-rule
|
||||
- default_rules = @enterprise.tag_rules.select(&:is_default)
|
||||
- current_rule_index = 0
|
||||
- if default_rules.empty?
|
||||
.no_rules
|
||||
= t('.no_rules_yet')
|
||||
- else
|
||||
- default_rules.each_with_index do |default_rule, index|
|
||||
- current_rule_index = index + 1
|
||||
= render TagRuleFormComponent.new(rule: default_rule, index: index)
|
||||
%hr
|
||||
|
||||
.add_rule.text-center
|
||||
%input.button.icon-plus{ type: 'button', value: t('.add_new_button'), "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "tag_rule_add_new_default_rule" }
|
||||
|
||||
= render AddTagRuleModalComponent.new(id: "tag_rule_add_new_default_rule", tag_rule_types: @tag_rule_types, current_index: current_rule_index, div_id: "default-tag-rule", is_default: true)
|
||||
|
||||
= render HelpModalComponent.new(id: "tag_rule_help_modal") do
|
||||
#tag-rule-help
|
||||
.margin-bottom-30.text-center
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
= turbo_stream.append "customer-tag-rule" do
|
||||
= render TagRuleGroupFormComponent.new(group: @group, index: @index, customer_rule_index: @customer_rule_index, tag_rule_types: @tag_rule_types)
|
||||
|
||||
= turbo_stream.remove_all ".no_tags"
|
||||
@@ -7,7 +7,7 @@
|
||||
%td.receival-details
|
||||
= text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => t('.receival_instructions_placeholder'), 'ng-model' => 'exchange.receival_instructions'
|
||||
- if type == 'distributor'
|
||||
%td.tags.panel-toggle.text-center{ name: "tags", "ng-if": 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' }
|
||||
%td.tags.panel-toggle.text-center{ name: "tags", id: "tags", "ng-if": 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' }
|
||||
{{ exchange.tags.length }}
|
||||
%td.collection-details
|
||||
= text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'ng-init' => 'setPickupTimeFieldDirty($index, exchange.pickup_time)', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'required' => 'required', 'placeholder' => t('.pickup_time_placeholder'), 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator', 'maxlength' => 35
|
||||
@@ -36,5 +36,5 @@
|
||||
- if type == 'distributor'
|
||||
%tr.panel-row{ object: "exchange",
|
||||
panels: "{products: 'exchange_products_distributed', tags: 'exchange_tags'}",
|
||||
locals: "$index,exchangeTotalVariants,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading",
|
||||
locals: "$index,exchangeTotalVariants,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading,order_cycle_form",
|
||||
colspan: 5 }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%h6= t('admin.product_import.index.choose_import_type')
|
||||
%br
|
||||
- options = { "#{t('admin.product_import.index.product_list')}" => :product_list }
|
||||
- options = options.merge("#{t('admin.product_import.index.inventories')}" => :inventories) if feature?(:inventory, spree_current_user.enterprises)
|
||||
- options = options.merge("#{t('admin.product_import.index.inventories')}" => :inventories) if feature?(:inventory, *spree_current_user.enterprises)
|
||||
= select_tag "settings[import_into]",
|
||||
options_for_select(options),
|
||||
{ "data-controller": "tom-select", class: "primary inline no-search", "ng-model": "settings.import_into" }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%i.icon-external-link
|
||||
= t('admin.product_import.index.product_list_template')
|
||||
|
||||
- if feature?(:inventory, spree_current_user.enterprises)
|
||||
- if feature?(:inventory, *spree_current_user.enterprises)
|
||||
%a.download{href: '/inventory_template.csv'}
|
||||
%i.icon-external-link
|
||||
= t('admin.product_import.index.inventory_template')
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
%td.col-inherits_properties.align-left
|
||||
.content= product.inherits_properties ? 'YES' : 'NO' #TODO: consider using https://github.com/RST-J/human_attribute_values, else use I18n.t (also below)
|
||||
%td.align-right
|
||||
= render(VerticalEllipsisMenu::Component.new) do
|
||||
= render(VerticalEllipsisMenuComponent.new) do
|
||||
= link_to t('admin.products_page.actions.edit'), edit_admin_product_path(product), 'data-turbo': false
|
||||
= link_to t('admin.products_page.actions.clone'), admin_clone_product_path(product), 'data-turbo-method': :post
|
||||
%a{ "data-controller": "modal-link", "data-action": "click->modal-link#setModalDataSetOnConfirm click->modal-link#open",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
= render partial: 'product_row', locals: { f: product_form, product:, product_index: }
|
||||
|
||||
- product.variants.each_with_index do |variant, variant_index|
|
||||
= form.fields_for("products][#{product_index}][variants_attributes][", variant, index: variant_index) do |variant_form|
|
||||
= form.fields_for("products][#{product_index}][variants_attributes", variant, index: variant_index) do |variant_form|
|
||||
%tr.condensed{ id: dom_id(variant), 'data-controller': "variant", 'class': "nested-form-wrapper", 'data-new-record': variant.new_record? ? "true" : false }
|
||||
= render partial: 'variant_row', locals: { variant:, f: variant_form, category_options:, tax_category_options:, producer_options: }
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
%th.align-left.col-unit_scale.with-input= t('admin.products_page.columns.unit_scale')
|
||||
%th.align-left.col-unit.with-input= t('admin.products_page.columns.unit')
|
||||
%th.align-left.col-price.with-input= t('admin.products_page.columns.price')
|
||||
%th.align-left.col-on_hand.with-input= t('admin.products_page.columns.on_hand')
|
||||
= render partial: 'spree/admin/shared/stimulus_sortable_header',
|
||||
locals: { column: :on_hand, sorted: params.dig(:q, :s), default: 'name asc' }
|
||||
%th.align-left.col-producer= t('admin.products_page.columns.producer')
|
||||
%th.align-left.col-category= t('admin.products_page.columns.category')
|
||||
%th.align-left.col-tax_category= t('admin.products_page.columns.tax_category')
|
||||
|
||||
@@ -80,11 +80,11 @@
|
||||
= error_message_on variant, :tax_category
|
||||
- if feature?(:variant_tag, spree_current_user)
|
||||
%td.col-tags.field.naked_inputs
|
||||
= render TagListInputComponent.new(form: f, method: "tag_list", tags: variant.tag_list, placeholder: t('.add_a_tag'), aria_label: t('admin.products_page.columns.tags'))
|
||||
= render TagListInputComponent.new(name: f.field_name(:tag_list), tags: variant.tag_list, placeholder: t('.add_a_tag'), aria_label: t('admin.products_page.columns.tags'))
|
||||
%td.col-inherits_properties.align-left
|
||||
-# empty
|
||||
%td.align-right
|
||||
= render(VerticalEllipsisMenu::Component.new) do
|
||||
= render(VerticalEllipsisMenuComponent.new) do
|
||||
- if variant.persisted?
|
||||
= link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(variant.product, variant)
|
||||
- if variant.product.variants.size > 1
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
- if @report_subtypes.present? && @report_subtypes.count > 1
|
||||
%input{type: 'hidden', name: 'report_subtype', value: @report_subtype}
|
||||
|
||||
.row.rendering-options{ "data-controller": "csv-select" }
|
||||
.row.rendering-options{ "data-controller": "csv-select metadata-toggle" }
|
||||
.alpha.two.columns
|
||||
= label_tag :report_format, t(".generate_report")
|
||||
.omega.fourteen.columns{ style: "margin-bottom: 1.5em;" }
|
||||
= select_tag :report_format, grouped_options_for_select({ |
|
||||
t('.formatted_data') => { t('.on_screen') => '', "PDF" => 'pdf', t('.spreadsheet') => 'xlsx' }, |
|
||||
t('.raw_data') => { "CSV" => 'csv' }, |
|
||||
}), { "data-csv-select-target": "reportType", "data-action": "csv-select#handleSelectChange" }
|
||||
}), { "data-csv-select-target": "reportType", "data-metadata-toggle-target": "reportType", "data-action": "csv-select#handleSelectChange metadata-toggle#handleSelectChange" }
|
||||
|
||||
- if @report.header_option? || @report.summary_row_option?
|
||||
- if @report.header_option? || @report.summary_row_option? || @report.metadata_option?
|
||||
.row
|
||||
.alpha.two.columns= label_tag nil, t(".display")
|
||||
.omega.fourteen.columns
|
||||
- if @report.metadata_option?
|
||||
%span.inline-checkbox{ style: "margin-right: 1rem;" }
|
||||
= check_box_tag :display_metadata_rows, true, @rendering_options.options[:display_metadata_rows], { "disabled": "true", "data-metadata-toggle-target": "checkbox" }
|
||||
= label_tag :display_metadata_rows, t(".metadata_rows"), {"class": "disabled", "data-metadata-toggle-target": "label" }
|
||||
- if @report.header_option?
|
||||
%span.inline-checkbox{ style: "margin-right: 1rem;" }
|
||||
= check_box_tag :display_header_row, true, @rendering_options.options[:display_header_row]
|
||||
|
||||
4
app/views/admin/tag_rules/destroy.turbo_stream.haml
Normal file
4
app/views/admin/tag_rules/destroy.turbo_stream.haml
Normal file
@@ -0,0 +1,4 @@
|
||||
- unless flash[:error]
|
||||
= turbo_stream.remove "tr_#{@index}"
|
||||
= turbo_stream.append "flashes" do
|
||||
= render(partial: 'admin/shared/flashes', locals: { flashes: flash })
|
||||
10
app/views/admin/tag_rules/new.turbo_stream.haml
Normal file
10
app/views/admin/tag_rules/new.turbo_stream.haml
Normal file
@@ -0,0 +1,10 @@
|
||||
- if flash[:error]
|
||||
= turbo_stream.append "flashes" do
|
||||
= render(partial: 'admin/shared/flashes', locals: { flashes: flash })
|
||||
- else
|
||||
= turbo_stream.append @div_id do
|
||||
= render(TagRuleFormComponent.new(rule: @default_rule,
|
||||
index: @index,
|
||||
customer_tags: @customer_tags,
|
||||
hidden_field_customer_tag_options: { "data-tag-rule-group-form-target": "ruleCustomerTag" }))
|
||||
= turbo_stream.remove_all ".no_rules"
|
||||
@@ -11,7 +11,7 @@
|
||||
= @order.shipping_method.name
|
||||
%em.fees= payment_or_shipping_price(@order.shipping_method, @order)
|
||||
.two-columns
|
||||
= render "delivery_details" if @order.shipping_method.delivery? || feature?(:hub_address)
|
||||
= render "delivery_details" if @order.shipping_method.delivery?
|
||||
- if @order.shipping_method.description.present?
|
||||
%div
|
||||
.summary-subtitle
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
.row
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%producer.active_table_node.row.animate-repeat{id: "{{producer.path}}",
|
||||
%producer.active_table_node.row.animate-repeat{
|
||||
"ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties:'supplied_properties')",
|
||||
"ng-controller" => "ProducerNodeCtrl",
|
||||
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
.row
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%producer.active_table_node.row.animate-repeat{id: "{{producer.path}}",
|
||||
%producer.active_table_node.row.animate-repeat{
|
||||
"ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties:'supplied_properties')",
|
||||
"ng-controller" => "ProducerNodeCtrl",
|
||||
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}",
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
%form{ name: 'about', novalidate: true, "ng-controller": "RegistrationFormCtrl", "ng-submit": "selectIfValid('images', about)" }
|
||||
.row
|
||||
.small-12.columns
|
||||
.alert-box.info{ "ofn-inline-alert": true, "ng-show": "visible" }
|
||||
.alert-box.info{ "data-controller": "toggle-control", "data-toggle-control-target": "content", style: "display: block;" }
|
||||
%h6{ "ng-bind" => "'registration.steps.about.success' | t:{enterprise: enterprise.name}" }
|
||||
%span= t(".registration_exit_message")
|
||||
%a.close{ "ng-click": "close()" } ×
|
||||
%a.close{ "data-action": "toggle-control#toggleDisplay" } ×
|
||||
|
||||
.small-12.large-8.columns
|
||||
.row
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.text-center.page-alert.fixed{ "ofn-page-alert" => true }
|
||||
.text-center.page-alert.fixed{ "data-controller" => "page-alert" }
|
||||
.alert-box
|
||||
= render 'shared/page_alert'
|
||||
%a.close{ "ng-click": "close()" } ×
|
||||
%a.close{ "data-action" => "page-alert#close" } ×
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.closed-shop-header
|
||||
.row
|
||||
.small-12.columns
|
||||
.content{ "darker-background" => true }
|
||||
.content
|
||||
%h4
|
||||
.warning-sign
|
||||
.rectangle
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user