Compare commits
375 commits
Author | SHA1 | Date | |
---|---|---|---|
|
4363cccc40 | ||
|
3610cec728 | ||
|
bce545979c | ||
|
cd57de01e6 | ||
|
b2cc25e716 | ||
|
63ca5015c9 | ||
|
dee71bf631 | ||
|
cdc7ca71bc | ||
|
6e39fca72c | ||
|
894c065b99 | ||
|
aa4e337cd0 | ||
|
618dbbe963 | ||
|
b195d169e1 | ||
|
b00ff321ab | ||
|
ec0b66c194 | ||
|
9fa0421c23 | ||
|
61fcad66c5 | ||
|
a23e96b4d6 | ||
|
dac3c9f6f7 | ||
|
78fd206c0c | ||
|
8b01b9844b | ||
|
01fe3c9741 | ||
|
69ef4e24c5 | ||
|
4ec5446a95 | ||
|
8627548d7c | ||
|
90d7e1f866 | ||
|
9ea8818e5b | ||
|
498646c10a | ||
|
53ad81aec3 | ||
|
f32fb6a4c4 | ||
|
55a154da83 | ||
|
a0d681509d | ||
|
f49cc3870d | ||
|
97cc1c1337 | ||
|
4ea6015d74 | ||
|
0566309c24 | ||
|
4d3883fe45 | ||
|
2df9ee64c4 | ||
|
382d25ce55 | ||
|
ad21aaf178 | ||
|
9d96f38b81 | ||
|
0f01a08f99 | ||
|
df0b6a6c18 | ||
|
aa1adc0ced | ||
|
ed81c5f052 | ||
|
578bcb63a2 | ||
|
e4735a255e | ||
|
0e51289625 | ||
|
02de28a7f5 | ||
|
3dfab91fb6 | ||
|
2d608a64dc | ||
|
d43f45196a | ||
|
59054f114e | ||
|
7dec9b40fb | ||
|
eb1e6fb30e | ||
|
8525db0d30 | ||
|
d284c25e62 | ||
|
5c70a32d36 | ||
|
79ca29fa03 | ||
|
a2980469e1 | ||
|
a84b7261d2 | ||
|
9a533b97dd | ||
|
d3f298645d | ||
|
aed62d49f9 | ||
|
be5aedef94 | ||
|
6c08749e8b | ||
|
d929a1fd5f | ||
|
4edd42604b | ||
|
6623f1ef19 | ||
|
855ecc3aa7 | ||
|
769907d812 | ||
|
222f5efde1 | ||
|
8a47750818 | ||
|
b77635f2c5 | ||
|
fdbac7a2ec | ||
|
91a7c02e25 | ||
|
9d8eb2256c | ||
|
d872730796 | ||
|
2b4f670b8c | ||
|
ebdc410fda | ||
|
f287ce8aa1 | ||
|
b8c1cd9ec8 | ||
|
2b17ec6a1c | ||
|
20a2992551 | ||
|
88ca4e9c11 | ||
|
b5adba84be | ||
|
c9b92919c8 | ||
|
c9294d62a1 | ||
|
e1e7331f89 | ||
|
01e3ad71ee | ||
|
6d94082c22 | ||
|
ad15739798 | ||
|
5838dde9eb | ||
|
c1e913d063 | ||
|
554a3f420f | ||
|
54992c9904 | ||
|
a12bdf7a74 | ||
|
96fc850a40 | ||
|
81dc5071dd | ||
|
7f04fe822a | ||
|
4a68135e33 | ||
|
9113a89cd2 | ||
|
fa32395161 | ||
|
e07cfbd1f5 | ||
|
a3a79ee9c5 | ||
|
5cec16c266 | ||
|
f8ac961520 | ||
|
6cbb10e0ec | ||
|
05a0f64a13 | ||
|
f4c44d486c | ||
|
4b074a464b | ||
|
593bbd4f2f | ||
|
5a480f0b31 | ||
|
c57cb099e5 | ||
|
6132172da5 | ||
|
b0577a18fb | ||
|
6a7c565cb4 | ||
|
5407d9111f | ||
|
07735ed6d9 | ||
|
6d95580155 | ||
|
6a716cc14e | ||
|
2816b81445 | ||
|
ec97a280ba | ||
|
f2b50e6d72 | ||
|
5d999f6fde | ||
|
45193886fc | ||
|
2d2267caf5 | ||
|
0feeb3d479 | ||
|
6a34f41239 | ||
|
3c58287629 | ||
|
140c1b9b8f | ||
|
e30feb1211 | ||
|
a4477a94e0 | ||
|
da806c2d87 | ||
|
52d812766a | ||
|
b19502dddf | ||
|
f693eff094 | ||
|
a527017bc8 | ||
|
ea29da0025 | ||
|
a92253c0a4 | ||
|
22eaceb6f5 | ||
|
e9e2d52c31 | ||
|
07aab1078e | ||
|
6a77e02e89 | ||
|
eb95f3e375 | ||
|
57154ae7e3 | ||
|
4ef67f595e | ||
|
6ad9fc2661 | ||
|
eb1f2f5e70 | ||
|
79e8b46019 | ||
|
31c24aad34 | ||
|
bb0c17a0a8 | ||
|
30f16915d9 | ||
|
58e0e8b22c | ||
|
83e900ba95 | ||
|
5979c7cb0e | ||
|
badcc0e38f | ||
|
db7a66c6a1 | ||
|
c72da3e761 | ||
|
e7e9d5fe02 | ||
|
e7b0d2b3ab | ||
|
fca30cb4f7 | ||
|
e1df68f209 | ||
|
948be17b92 | ||
|
d1d2037bf0 | ||
|
523f4bf2a9 | ||
|
a29835d6fc | ||
|
84d36f7c33 | ||
|
3ba2f1662e | ||
|
adfc893c95 | ||
|
4ab7311435 | ||
|
c48f50195a | ||
|
df461b8555 | ||
|
d9c31e8871 | ||
|
ccdc39ce05 | ||
|
b4af719779 | ||
|
94e175153e | ||
|
9cb449f7b5 | ||
|
e991e13cbb | ||
|
af203e9260 | ||
|
60f5c77685 | ||
|
e67e1e6690 | ||
|
277d1924b4 | ||
|
415c2edf0e | ||
|
2fd1a1088b | ||
|
c7c3065834 | ||
|
53bea71089 | ||
|
222a90d755 | ||
|
e7dc2343f4 | ||
|
ca5fbfa3b9 | ||
|
60d093d7a0 | ||
|
30cd87cad9 | ||
|
155a2d5716 | ||
|
754e1e8245 | ||
|
46b7ff03fa | ||
|
1e821105e0 | ||
|
716c418205 | ||
|
3238ff3a42 | ||
|
c17f4cf78e | ||
|
c1cc4bd5d5 | ||
|
6f9ce78d50 | ||
|
e1a22abab0 | ||
|
c0ff7028f2 | ||
|
a3a70707ae | ||
|
782b4781ea | ||
b25e9e4a2b | |||
804cbfac37 | |||
53f6327da0 | |||
|
ea03321667 | ||
fec8645764 | |||
|
61ae452aec | ||
ef796ebd75 | |||
|
4a80d07681 | ||
1efac9612d | |||
548d466bd0 | |||
d0dfc39da0 | |||
|
eef81f506b | ||
bf13ca3e53 | |||
|
45bbd5dd81 | ||
bd59c6b7bc | |||
1481edae95 | |||
|
1912a779e0 | ||
|
f09d4c1f72 | ||
|
dd7b4df5da | ||
|
3a57c25f9b | ||
|
889797330a | ||
|
3475f369be | ||
|
295d89490c | ||
|
f5adbfc367 | ||
|
6a4118fa17 | ||
|
74991719e2 | ||
|
6886824347 | ||
|
9648513fb9 | ||
|
055f8a6f83 | ||
|
889476fa89 | ||
|
79401b25bc | ||
|
75ff9528f9 | ||
|
0212ddf823 | ||
|
a877cf500c | ||
|
5e1f52a57e | ||
|
0720148f9e | ||
|
19d1f1b179 | ||
|
33afc77c09 | ||
|
f9b872e628 | ||
|
92a5673614 | ||
|
baa1f3d581 | ||
|
68343b7e49 | ||
|
d3448908e7 | ||
|
a0613d83e5 | ||
|
31865ae3ea | ||
|
2d75f50a08 | ||
|
56b1eb81d2 | ||
|
24efcdf23a | ||
|
2142e2a01f | ||
|
7d0c1ac904 | ||
|
147ebb02ab | ||
|
c8cdedce3f | ||
|
68522a8e7e | ||
|
3c0f053028 | ||
|
239c5d43a8 | ||
|
da07d142ac | ||
|
a18de2625e | ||
|
9018d547af | ||
|
cc4e8c9c5b | ||
|
6fa5e3d511 | ||
|
8518b43d03 | ||
|
4eecba2d86 | ||
|
606de96a91 | ||
|
af86ba2821 | ||
|
6763a237e9 | ||
|
2358e0ec0a | ||
|
f698e1c7b2 | ||
|
55a52667bd | ||
|
1b2f0d053a | ||
|
db56c05725 | ||
|
a73d8ef737 | ||
|
b8c1305eca | ||
|
635114633d | ||
|
228488bec2 | ||
|
3d8eb0cebe | ||
|
e1f98191b5 | ||
|
9cd0f833aa | ||
|
c83869f9ec | ||
|
1fb010e421 | ||
|
13c532da0e | ||
|
52817d9e08 | ||
|
36d973dbae | ||
|
f8d1febe47 | ||
|
3a0ee18ceb | ||
|
a1f23559ad | ||
|
37d8eb660b | ||
|
c51fb0fd8a | ||
|
b7760f21e9 | ||
|
901af9b767 | ||
|
65fcb2ed46 | ||
|
c7da9a0ca7 | ||
|
2060b29e20 | ||
540564b5f9 | |||
|
c79e3ab064 | ||
|
78a299ac42 | ||
|
09b8902e9c | ||
|
b0524bd069 | ||
|
d8bbbc2d8a | ||
|
65ee374c94 | ||
|
51a9ab3805 | ||
|
9394cd3524 | ||
|
a04886c57f | ||
|
70286efabc | ||
|
b546a9c96c | ||
|
5055bb710c | ||
|
da3a828560 | ||
|
c82faf616a | ||
|
af90b22c3e | ||
|
c836c2b293 | ||
|
86e87be8d9 | ||
|
48be7c2abb | ||
|
bd683e9678 | ||
|
90b4e5f608 | ||
|
15c62b2cf9 | ||
|
63f9a15564 | ||
|
3c0705315d | ||
|
2713e089c8 | ||
|
2db51fde4a | ||
|
c7f39563a7 | ||
|
db89f73de5 | ||
|
c110053075 | ||
|
1866514483 | ||
|
5484c56063 | ||
|
7ddcafbaba | ||
|
65482423d6 | ||
|
8c1dfbd065 | ||
|
a5f77ec638 | ||
|
eb169b9fc8 | ||
|
cc3867d275 | ||
|
610acefcfb | ||
|
fead96c4ca | ||
|
83b668deb3 | ||
|
7c6e55c60a | ||
|
88363fa962 | ||
|
c92345b630 | ||
|
4cf127d3d0 | ||
|
da8cccb180 | ||
|
5d865a2528 | ||
|
4a90087e61 | ||
|
a0541627d7 | ||
|
06843a2786 | ||
|
dc630be64f | ||
|
524d26aa72 | ||
|
2202557584 | ||
|
8e888dcc2f | ||
|
bc498ff0a7 | ||
|
c8c3b10329 | ||
|
8a28888c3a | ||
|
4273c99886 | ||
|
59e9c69f5b | ||
|
f301687739 | ||
|
9c71bc36a0 | ||
|
cbc6eaef93 | ||
|
d11af323d8 | ||
|
01d970af8f | ||
|
1b8328b941 | ||
|
56bc008af4 | ||
|
e649a865d4 | ||
|
60dbb8dd0e | ||
|
b5b6419ca4 | ||
|
7ad9ae7886 | ||
|
b04a9ce701 | ||
|
8fd4a8fe35 | ||
|
6a326e471f | ||
|
580c341c99 | ||
|
52218b0d22 | ||
|
8af75f020c | ||
|
c16decd960 | ||
|
b4693c398a | ||
|
b77381c075 |
161 changed files with 27945 additions and 5051 deletions
35
.docker/Dockerfile
Normal file
35
.docker/Dockerfile
Normal file
|
@ -0,0 +1,35 @@
|
|||
FROM php:7.1-fpm
|
||||
|
||||
RUN apt-get update
|
||||
|
||||
RUN apt-get install -y zlib1g-dev libpq-dev git libicu-dev libxml2-dev libpng-dev libjpeg-dev libmcrypt-dev libxslt-dev libfreetype6-dev \
|
||||
&& docker-php-ext-configure intl \
|
||||
&& docker-php-ext-install intl \
|
||||
&& docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
|
||||
&& docker-php-ext-install mysqli pdo pdo_mysql \
|
||||
&& docker-php-ext-install zip \
|
||||
&& docker-php-ext-install xml \
|
||||
&& docker-php-ext-configure gd --with-png-dir=/usr/local/ --with-jpeg-dir=/usr/local/ --with-freetype-dir=/usr/local/ \
|
||||
&& docker-php-ext-install gd \
|
||||
&& docker-php-ext-install mcrypt \
|
||||
&& docker-php-ext-install bcmath \
|
||||
&& docker-php-ext-install soap \
|
||||
&& docker-php-ext-install xsl \
|
||||
&& docker-php-ext-install mbstring
|
||||
|
||||
RUN apt-get install -y gettext
|
||||
RUN apt-get install -y subversion
|
||||
RUN apt-get install -y wget
|
||||
|
||||
RUN wget -O /usr/bin/phpunit https://phar.phpunit.de/phpunit-7.phar && chmod +x /usr/bin/phpunit
|
||||
RUN curl --insecure https://getcomposer.org/download/1.9.3/composer.phar -o /usr/bin/composer && chmod +x /usr/bin/composer
|
||||
|
||||
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Set timezone
|
||||
RUN rm /etc/localtime
|
||||
RUN ln -s /usr/share/zoneinfo/Europe/Moscow /etc/localtime
|
||||
RUN "date"
|
||||
|
||||
WORKDIR /code
|
12
.env-dist
Normal file
12
.env-dist
Normal file
|
@ -0,0 +1,12 @@
|
|||
# MySQL host and credentials
|
||||
DB_NAME=wc_retailcrm_test
|
||||
DB_USER=wc_retailcrm
|
||||
DB_PASS=wc_retailcrm
|
||||
DB_HOST=mysql
|
||||
|
||||
# WordPress and WooCommerce versions
|
||||
WP_VERSION=5.3
|
||||
WC_VERSION=3.9.0
|
||||
|
||||
# Enable this in order to pipe all module log messages (including debug ones) to STDOUT.
|
||||
MODULE_LOGS_TO_STDOUT=0
|
159
.github/workflows/woo.yml
vendored
Normal file
159
.github/workflows/woo.yml
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
name: woo
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
tags-ignore:
|
||||
- '*.*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_USER: root
|
||||
DB_PASS: root
|
||||
DB_NAME: wc_retailcrm_test
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# WordPress 5.3
|
||||
#PHP 7.1 and 7.4
|
||||
- php-version: '7.1'
|
||||
wp: '5.3'
|
||||
wc: '5.4.3'
|
||||
coverage: 1
|
||||
phpunit: 'phpunit:7.5.20'
|
||||
- php-version: '7.1'
|
||||
wp: '5.3'
|
||||
wc: '6.4.0'
|
||||
phpunit: 'phpunit:7.5.20'
|
||||
- php-version: '7.4'
|
||||
wp: '5.3'
|
||||
wc: '5.4.3'
|
||||
phpunit: 'phpunit:7.5.20'
|
||||
- php-version: '7.4'
|
||||
wp: '5.3'
|
||||
wc: '6.4.0'
|
||||
phpunit: 'phpunit:7.5.20'
|
||||
|
||||
# WordPress 6.0
|
||||
# PHP 7.1 and 7.4
|
||||
- php-version: '7.1'
|
||||
wp: '6.0'
|
||||
wc: '5.4.3'
|
||||
phpunit: 'phpunit:7.5.20'
|
||||
- php-version: '7.1'
|
||||
wp: '6.0'
|
||||
wc: '6.4.0'
|
||||
phpunit: 'phpunit:7.5.20'
|
||||
- php-version: '7.4'
|
||||
wp: '6.0'
|
||||
wc: '5.4.3'
|
||||
phpunit: 'phpunit:7.5.20'
|
||||
- php-version: '7.4'
|
||||
wp: '6.0'
|
||||
wc: '6.4.0'
|
||||
phpunit: 'phpunit:7.5.20'
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: false
|
||||
MYSQL_ROOT_PASSWORD: ${{ env.DB_PASS }}
|
||||
MYSQL_DATABASE: ${{ env.DB_NAME }}
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup PHP ${{ matrix.php-version }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer:2.1.14, ${{ matrix.phpunit }}
|
||||
extensions: gd, mbstring, mysqli, zip, unzip, mcrypt, mysql, pdo_mysql, dom
|
||||
coverage: xdebug
|
||||
|
||||
- name: Tool versions
|
||||
run: |
|
||||
php --version
|
||||
composer --version
|
||||
phpunit --version
|
||||
|
||||
- name: Install Polyfills dependency for WP 5.9 and 6.0
|
||||
if: ${{ matrix.wp }} == '6.0' || ${{ matrix.wp }} == '5.9'
|
||||
run: |
|
||||
composer require --dev yoast/phpunit-polyfills --ignore-platform-reqs
|
||||
|
||||
- name: Install Woocommerce
|
||||
env:
|
||||
WP_VERSION: ${{ matrix.wp }}
|
||||
WC_VERSION: ${{ matrix.wc }}
|
||||
run: make install
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
WP_VERSION: ${{ matrix.wp }}
|
||||
WC_VERSION: ${{ matrix.wc }}
|
||||
run: make test
|
||||
|
||||
- name: Coverage
|
||||
env:
|
||||
COVERAGE: ${{ matrix.coverage }}
|
||||
if: env.COVERAGE == 1
|
||||
run: |
|
||||
make coverage
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
deploy:
|
||||
needs: ['test']
|
||||
if: success() && github.event_name == 'push' && github.repository_owner == 'retailcrm' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup PHP 7.2
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '7.2'
|
||||
tools: composer:v2
|
||||
- name: Build release
|
||||
run: |
|
||||
git fetch origin --unshallow --tags
|
||||
export LAST_TAG=`git describe --abbrev=0 --tags`
|
||||
export VERSION=`cat VERSION`
|
||||
export ARCHIVE_NAME=retailcrm-$VERSION.zip
|
||||
export ARCHIVE_PATH="/tmp/$ARCHIVE_NAME"
|
||||
export RELEASE_TAG=v$VERSION
|
||||
export LAST_COMMIT=`git log --oneline --format=%B -n 1 HEAD | head -n 1`
|
||||
echo RELEASE_TAG=$RELEASE_TAG >> $GITHUB_ENV
|
||||
echo LAST_TAG=$LAST_TAG >> $GITHUB_ENV
|
||||
echo LAST_COMMIT=$LAST_COMMIT >> $GITHUB_ENV
|
||||
echo ARCHIVE_PATH=$ARCHIVE_PATH >> $GITHUB_ENV
|
||||
echo ARCHIVE_NAME=$ARCHIVE_NAME >> $GITHUB_ENV
|
||||
make build_archive
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
if: env.LAST_TAG != env.RELEASE_TAG
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_TAG }}
|
||||
release_name: ${{ env.RELEASE_TAG }}
|
||||
body: ${{ env.LAST_COMMIT }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Deploy
|
||||
env:
|
||||
SVNREPOURL: ${{ secrets.SVNREPOURL }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
run: |
|
||||
make svn_clone
|
||||
make svn_push
|
||||
- name: Cleanup
|
||||
if: env.LAST_TAG != env.RELEASE_TAG
|
||||
run: make remove_dir
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1 +1,4 @@
|
|||
/nbproject/private/
|
||||
/nbproject/
|
||||
/vendor/
|
||||
.idea/
|
||||
.env
|
||||
|
|
439
CHANGELOG.md
Normal file
439
CHANGELOG.md
Normal file
|
@ -0,0 +1,439 @@
|
|||
## 2025-02-19 4.8.21
|
||||
* Fix version of module
|
||||
|
||||
## 2025-02-18 4.8.20
|
||||
* Add returned types for methods offsetExists, offsetSet, offsetUnset in WC_Retailcrm_Response
|
||||
|
||||
## 2025-02-04 4.8.19
|
||||
* Optimization of order unloading
|
||||
|
||||
## 2025-02-03 4.8.18
|
||||
* Added additional parameters to GET requests
|
||||
|
||||
## 2025-01-21 4.8.17
|
||||
* Fix deploy
|
||||
|
||||
## 2025-01-13 4.8.16
|
||||
* Fix tests svn error
|
||||
|
||||
## 2024-12-19 4.8.15
|
||||
* Fix uploading archive in CRM using console script
|
||||
|
||||
## 2024-11-07 4.8.14
|
||||
* The method for determining the stock quantity has been optimized
|
||||
|
||||
## 2024-11-07 4.8.13
|
||||
* Supports custom cart and checkout templates
|
||||
|
||||
## 2024-10-24 4.8.12
|
||||
* Fixed multiple execution of order updates
|
||||
|
||||
## 2024-10-14 4.8.11
|
||||
* Added additional parameters to GET requests
|
||||
|
||||
## 2024-10-08 4.8.10
|
||||
* Fixed errors in catalog formation when changing synchronization parameters (sku/externalId)
|
||||
|
||||
## 2024-09-30 4.8.9
|
||||
* Improvement of customer registration form in loyalty program
|
||||
|
||||
## 2024-09-30 4.8.8
|
||||
* Fix tests svn externals definitions error
|
||||
|
||||
## 2024-09-26 4.8.7
|
||||
* Logs refactoring
|
||||
|
||||
## 2024-09-26 4.8.6
|
||||
* Optimized url-validator
|
||||
|
||||
## 2024-09-20 4.8.5
|
||||
* Project testing has been updated
|
||||
|
||||
## 2024-09-13 4.8.4
|
||||
* Updated work with promotional items when loyalty program is enabled
|
||||
|
||||
## 2024-09-11 4.8.3
|
||||
* Added loyalty program coupon entry in the form by click
|
||||
|
||||
## 2024-08-26 4.8.2
|
||||
* Fixed base file customization issue
|
||||
* Added a hook to update the list of meta fields
|
||||
|
||||
## 2024-08-06 4.8.1
|
||||
* Fix filtering of api query results
|
||||
|
||||
## 2024-07-15 4.8.0
|
||||
* Added loyalty program
|
||||
|
||||
## 2024-06-27 4.7.9
|
||||
* Fixed undefined array key number in order history
|
||||
|
||||
## 2024-06-26 4.7.8
|
||||
* Added passing link field for abandoned baskets
|
||||
|
||||
## 2024-04-23 4.7.7
|
||||
* Added transfer of services via ICML catalog
|
||||
|
||||
## 2024-04-22 4.7.6
|
||||
* Support WP 6.5
|
||||
|
||||
## 2024-04-19 4.7.5
|
||||
* Added automatic catalog generation when changing "Activate the binding via sku (xml)"
|
||||
|
||||
## 2024-02-29 4.7.4
|
||||
* Fixed an error when transferring abandoned carts
|
||||
|
||||
## 2024-02-07 4.7.3
|
||||
* Added filters after creating and updating an order
|
||||
|
||||
## 2024-01-31 4.7.2
|
||||
* Fixed error with send address by history
|
||||
|
||||
## 2024-01-30 4.7.1
|
||||
* Fixed the error of displaying the 'Add' button to mapping custom fields
|
||||
|
||||
## 2023-12-07 4.7.0
|
||||
* Added support WooCommerce 8.2 (HPOS)
|
||||
|
||||
## 2023-11-20 4.6.14
|
||||
* Fix module activation/deactivation
|
||||
|
||||
## 2023-10-26 4.6.13
|
||||
* Fix not correct scoring product total price when product price is not number
|
||||
|
||||
## 2023-10-02 4.6.12
|
||||
* Added currency validation when configuring the module
|
||||
|
||||
## 2023-08-31 4.6.11
|
||||
* Added the ability to work with coupons through the CRM system
|
||||
|
||||
## 2023-07-19 4.6.10
|
||||
* Abandoned cart transfer fix
|
||||
|
||||
## 2023-07-19 4.6.9
|
||||
* Changed the logic of customer subscriptions to promotional newsletters
|
||||
|
||||
## 2023-06-27 4.6.8
|
||||
* Added the ability to select CRM warehouses to synchronize the balance of offers
|
||||
|
||||
## 2023-06-27 4.6.7
|
||||
* Fixed customer phone sending to crm when order will create by guest
|
||||
|
||||
## 2022-06-14 4.6.6
|
||||
* Added handling of fatal errors when working with abandoned carts
|
||||
|
||||
## 2022-06-08 4.6.5
|
||||
* Transferring WC meta fields to standard CRM order and customer fields
|
||||
|
||||
## 2022-05-30 4.6.4
|
||||
* Optimizing unloading of stock
|
||||
|
||||
## 2022-05-29 4.6.3
|
||||
* Types of deliveries and payments are displayed only for available stores
|
||||
|
||||
## 2022-05-17 4.6.2
|
||||
* Modified method getting an address by history
|
||||
|
||||
## 2022-04-25 4.6.1
|
||||
* The algorithm for getting the history of orders and customers has been optimized
|
||||
|
||||
## 2022-03-17 4.6.0
|
||||
* Added functionality of abandoned carts
|
||||
|
||||
## 2023-03-02 4.5.4
|
||||
* Fix display payment methods
|
||||
|
||||
## 2022-12-26 4.5.3
|
||||
* Fix bug with products tax
|
||||
|
||||
## 2022-11-09 4.5.2
|
||||
* Add validator for CRM URL
|
||||
|
||||
## 2022-11-09 4.5.1
|
||||
* Correction of RAM overflow during ICMP product catalog generation.
|
||||
|
||||
## 2022-09-30 4.5.0
|
||||
* Fix path for js scripts
|
||||
* Migrating to PHP 7.0.
|
||||
* Change logic work with ICML catalog: added streaming generation, added generators in the ICML generation process.
|
||||
* Change logic work with address
|
||||
|
||||
## 2022-09-05 4.4.9
|
||||
* Fix bug with product tax
|
||||
|
||||
## 2022-09-02 4.4.8
|
||||
* Fix a critical bug when working with taxes
|
||||
|
||||
## 2022-08-10 4.4.7
|
||||
* Add support for payment method on delivery
|
||||
|
||||
## 2022-08-06 4.4.6
|
||||
* Add automatically upload ICML in CRM
|
||||
* Add filter for changing ICML product information
|
||||
* Important fix bug with shipping tax
|
||||
|
||||
## 2022-07-18 4.4.5
|
||||
* Change logic work with delivery cost
|
||||
* Add price rounding from WC settings
|
||||
* Add functionality for changing the time interval for cron tasks
|
||||
* Fix error with empty 'paidAt'
|
||||
* Change processing history by sinceId
|
||||
* Fix spanish accents processing in ICML
|
||||
* Fix WA icon positioning
|
||||
|
||||
## 2022-05-26 4.4.4
|
||||
* Add product description to ICML
|
||||
* Fix fatal error using API without api_key
|
||||
* Add priceType processing to CRM order by history
|
||||
* Add method in API V5 and delete use another version
|
||||
* Fix error with integration payments
|
||||
* Fix bug with changing order status by history
|
||||
|
||||
## 2022-03-24 4.4.3
|
||||
* Fix bug in updating order number by history
|
||||
* Add multiple image transfer in ICML
|
||||
* Add filters for custom fields
|
||||
* Fix bug with create/update customer
|
||||
|
||||
## 2022-02-24 4.4.2
|
||||
* Delete deprecated API V4. Refactoring API V5 and history getting method
|
||||
* Fix bug with use xmlId
|
||||
* Add order number transfer CMS -> CRM by history
|
||||
* Add documentation for registering client functionality
|
||||
* Delete legacy code for update customer name and surname
|
||||
|
||||
## 2022-01-17 4.4.1
|
||||
* Added functionality to skip some orders statuses
|
||||
* Improved the create/update method when registering customers
|
||||
* Add mapping metadata fields in settings
|
||||
* Improvement of the user interface, plugin operation, fix bugs
|
||||
|
||||
## 2021-12-15 4.4.0
|
||||
* Migrating to PHP 5.6. We tested the module, improved performance, security and test coverage.
|
||||
* Add validate countryIso. Fix bug with duplicate customer address
|
||||
* Fix bugs in history
|
||||
* Fix PHP warning and deprecated
|
||||
* Add documentation for module
|
||||
|
||||
## 2021-08-30 4.3.8
|
||||
* Updated logic work address
|
||||
* Added transfer of the client's comment to the WC order
|
||||
* Added the ability to skip inactive statuses in settings module
|
||||
* Deleted option 'Do not transmit the cost of delivery'
|
||||
* Fix bug in archive upload
|
||||
|
||||
## 2021-08-04 4.3.7
|
||||
* Fixed an error with incorrect unloading of archived clients
|
||||
* Removed the "Client roles" option from the module settings
|
||||
|
||||
## 2021-07-27 4.3.6
|
||||
* Updated the presentation of the module settings
|
||||
* Fixed a bug connected with adding variable products to the catalogue as usual products
|
||||
|
||||
## 2021-07-22 4.3.5
|
||||
* Updated version in Marketplace
|
||||
|
||||
## 2021-07-22 4.3.4
|
||||
* Minor bugs were fixed
|
||||
|
||||
## 2021-07-21 4.3.3
|
||||
* Redesigned the WhatsApp icon
|
||||
* Added the ability to enable extended logging in the module settings
|
||||
* Added the"Debug info" block
|
||||
* Improved ICML catalogue generation
|
||||
* Added batch export of archived orders and customers
|
||||
|
||||
## 2021-07-05 4.3.2
|
||||
* Minor bugs were fixed
|
||||
|
||||
## 2021-07-02 4.3.1
|
||||
* Rebranding of RetailCRM module --> Simla.com
|
||||
|
||||
## 2021-06-30 4.3.0
|
||||
* Rebranding of RetailCRM module --> Simla.com
|
||||
* Fixed a bug in the "Activate link by sku (xmlId)" option
|
||||
* Added the ability to use the WhatsApp chat link on the site
|
||||
* Fixed minor bugs in the history and generation of the ICML catalogue
|
||||
|
||||
## 2021-03-15 4.2.4
|
||||
* Added a display of the total number of variable products
|
||||
* Added validation for the order creation date
|
||||
* Added validation for orders with auto-draft status
|
||||
* Updated WP and WC versions in local tests
|
||||
* Fixed a bug in the "Transfer of order number" option
|
||||
|
||||
## 2021-01-20 4.2.3
|
||||
* Updated version in the Marketplace
|
||||
|
||||
## 2020-12-17 4.2.2
|
||||
* RetailCRM’s redesign
|
||||
|
||||
## 2020-12-15 4.2.1
|
||||
* RetailCRM’s redesign
|
||||
|
||||
## 2020-12-02 4.2.0
|
||||
* Fixed a bug connected with receiving the date of creation of an unregistered user
|
||||
* Changed the logic of payments by deleting the "Transfer of payment amount" option
|
||||
* Fixed the “shipping” address bug. If it is empty, use a “billing” address
|
||||
* Added Spanish and English translations of the main page
|
||||
* Fixed a bug connected with deleting products when using the "Activate link by sku (xmlId)" option
|
||||
|
||||
## 2020-09-21 4.1.5
|
||||
* Fixed a bug connected with transferring emails. Before being sent to RetailCRM, emails are always converted to lower case.
|
||||
* Fixed a bug connected with transfers of payments of no amount
|
||||
* Improved the work of discounts in the order
|
||||
|
||||
## 2020-08-27 4.1.4
|
||||
* Added translations to transfer the prime cost of the delivery
|
||||
* Fixed a bug connected with an incorrect displaying the Live Chat on the login page
|
||||
|
||||
## 2020-08-20 4.1.3
|
||||
* Added translations for the option of customers’ role
|
||||
* Added the ability to optionally transfer the prime cost of delivery
|
||||
|
||||
## 2020-08-20 4.1.2
|
||||
* Fixed a bug connected with missing data books settings
|
||||
|
||||
## 2020-08-08 4.1.1
|
||||
* Added a setting for selecting customer roles for upload to RetailCRM
|
||||
|
||||
## 2020-08-05 4.1.0
|
||||
* Added the ability to connect the Live Chat
|
||||
|
||||
## 2020-07-28 4.0.1
|
||||
* Fixed transfer of payment status
|
||||
|
||||
## 2020-06-18 4.0.0
|
||||
* Support for corporate customers
|
||||
* Support for changing a customer in an order
|
||||
|
||||
## 2020-06-18 3.6.4
|
||||
* Passing the region / state / province name instead of the code
|
||||
|
||||
## 2020-06-10 3.6.3
|
||||
* Improved order data updating by history
|
||||
|
||||
## 2020-04-13 3.6.2
|
||||
* Fixed a bug that led to duplication of some customers
|
||||
|
||||
## 2019-03-31 3.6.1
|
||||
* Fixed a bug connected with generating a product catalogue
|
||||
|
||||
## 2020-03-25 3.6.0
|
||||
* Added the setting for transferring the payment amount
|
||||
|
||||
## 2019-10-07 3.5.4
|
||||
* Added the ability to process identical product items
|
||||
|
||||
## 2019-04-22 3.5.2
|
||||
* Fixed a bug connected with exporting orders to RetailCRM
|
||||
* Fixed a translation error
|
||||
|
||||
## 2019-04-16 3.5.1
|
||||
* Fixed a bug connected with plugin activation
|
||||
|
||||
## 2019-03-06 3.5.0
|
||||
* Added a setting to deactivate uploading order changes to RetailCRM
|
||||
* Added a setting for activating SKU exporting to xmlId and linking products by the “xmlId” field
|
||||
* Added a setting for transferring order numbers to RetailCRM
|
||||
|
||||
## 2019-03-06 3.4.5
|
||||
* Fixed a bug connected with adding a discount when decreasing the quantity of a product
|
||||
* Moved the initialization of the settings form after the initialization of all plugins
|
||||
|
||||
## 2019-02-25 3.4.4
|
||||
* Added generation of a unique id to the externalId of the payment being sent
|
||||
|
||||
## 2019-02-15 3.4.3
|
||||
* Fixed saving of the payment type when creating an order when processing the history of changes on the WC side
|
||||
* Fixed saving of the payment type when changing an order when processing the history of changes on the WC side
|
||||
* Fixed connecting files using the checkCustomFile method
|
||||
|
||||
## 2019-02-07 3.4.2
|
||||
* Fixed change of payment type on the WC side
|
||||
* Added inactive payment types in the settings
|
||||
* Removed external code generation of a customer
|
||||
|
||||
## 2019-01-22 v3.4.1
|
||||
* Fixed archive export of customers
|
||||
|
||||
## 2019-01-17 v3.4.0
|
||||
* Added Daemon Collector setting
|
||||
* Changed the logic of data transfer for orders and customers. Delivery data is transferred to the order, payment data to the customer card.
|
||||
|
||||
## 2018-12-14 v3.3.8
|
||||
* Added export of images for product categories to ICML
|
||||
|
||||
## 2018-12-11 v3.3.7
|
||||
* Fixed a bug connected with activation
|
||||
|
||||
## 2018-12-06 v3.3.6
|
||||
* Fixed module activation in RetailCRM’s marketplace when using api v4
|
||||
* Expanded configuration for sending
|
||||
|
||||
## 2018-10-25 v3.3.5
|
||||
* Added module activation in RetailCRM’s marketplace
|
||||
|
||||
## 2018-08-30 v3.3.4
|
||||
* Fixed a bug connected with zeroing the quantity of the product in the WC order
|
||||
|
||||
## 2018-08-30 v3.3.3
|
||||
* Added buttons to go to the plugin settings and to generate a catalogue in the WordPress admin panel
|
||||
* Added transfer of payment status on v5
|
||||
|
||||
## 2018-08-22 v3.3.2
|
||||
* Removed check for the existence of tasks in wp-cron on every page loading
|
||||
* Tasks in wp-cron are now activated in the plugin settings
|
||||
|
||||
## 2018-08-09 v3.3.1
|
||||
* Fixed a bug connected with duplication of products from WC
|
||||
|
||||
## 2018-08-06 v3.3.0
|
||||
* Reworked the mechanics of handling change history (added merging of all changes)
|
||||
* Added filter "retailcrm_history_before_save" to modify the history data
|
||||
|
||||
## 2018-07-19 v3.2.0
|
||||
* Improved the method for selection of data on deliveries and payments in the plugin settings. (All types of payments are selected, not just allowed ones. Deliveries that are created for individual zones are transferred as services)
|
||||
* Fixed bugs when processing change history
|
||||
* Added tests for processing history of changes
|
||||
|
||||
## 2018-06-19 v3.1.1
|
||||
* Fixed the code for sending data to UA
|
||||
* Added new filters, and added transfer of new parameters to existing ones
|
||||
|
||||
## 2018-05-28 v3.1.0
|
||||
* Added the ability to manually export orders to the plugin settings interface
|
||||
* Fixed initialization of the UA code for sending orders on all pages
|
||||
|
||||
## 2018-04-26 v3.0.0
|
||||
* Added tests
|
||||
* Refactoring of the code
|
||||
* Webhooks added
|
||||
|
||||
## 2018-03-22 v2.1.4
|
||||
* Fixed a bug connected with the activated module without settings
|
||||
* Added a filter when forming an order array
|
||||
* Fixed generation of icml with incomplete product picture
|
||||
|
||||
## 2018-03-22 v2.1.3
|
||||
* Fixed a bug on php5.3
|
||||
|
||||
## 2018-03-21 v2.1.2
|
||||
* Added plugin localization
|
||||
* Added integration with UA
|
||||
|
||||
## 2018-03-12 v2.1.1
|
||||
* Fixed a bug connected with editing customer information
|
||||
|
||||
## 2018-02-26 v2.1.0
|
||||
* Reworked mechanics of generating icml product catalog
|
||||
* Added tax rate export to icml catalog
|
||||
* Fixed recalculation of totals after changing the quantity of a product in RetailCRM
|
||||
|
||||
## 2018-02-19 v2.0.6
|
||||
* Fixed occurrence of a Warning in PHP 7.2 when generating a product catalog
|
||||
* Added a setting for exporting orders from RetailCRM with certain order methods
|
||||
* Changes are exported from RetailCRM by sinceId
|
||||
|
||||
## 2018-02-02 v2.0.5
|
||||
* Fixed an incorrect calculation of discounts for products
|
51
Makefile
Normal file
51
Makefile
Normal file
|
@ -0,0 +1,51 @@
|
|||
ROOT_DIR=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
VERSION = `cat $(ROOT_DIR)/VERSION`
|
||||
ARCHIVE_NAME = '/tmp/retailcrm-'$(VERSION)'.zip'
|
||||
|
||||
.PHONY: test
|
||||
|
||||
svn_clone:
|
||||
sudo apt install subversion
|
||||
mkdir -p /tmp/svn_plugin_dir
|
||||
svn co $(SVNREPOURL) /tmp/svn_plugin_dir --no-auth-cache
|
||||
|
||||
svn_push: /tmp/svn_plugin_dir
|
||||
if [ ! -d "/tmp/svn_plugin_dir/tags/$(VERSION)" ]; then \
|
||||
svn delete /tmp/svn_plugin_dir/trunk/*; \
|
||||
rm -rf /tmp/svn_plugin_dir/trunk/*; \
|
||||
cp -R $(ROOT_DIR)/src/* /tmp/svn_plugin_dir/trunk; \
|
||||
svn copy /tmp/svn_plugin_dir/trunk /tmp/svn_plugin_dir/tags/$(VERSION) --no-auth-cache; \
|
||||
svn add /tmp/svn_plugin_dir/trunk/* --force; \
|
||||
svn add /tmp/svn_plugin_dir/tags/$(VERSION)/* --force; \
|
||||
svn ci /tmp/svn_plugin_dir -m $(VERSION) --username $(USERNAME) --password $(PASSWORD) --no-auth-cache; \
|
||||
fi
|
||||
|
||||
remove_dir:
|
||||
rm -rf /tmp/svn_plugin_dir
|
||||
|
||||
compile_pot:
|
||||
msgfmt resources/pot/retailcrm-ru_RU.pot -o src/languages/retailcrm-ru_RU.mo
|
||||
msgfmt resources/pot/retailcrm-es_ES.pot -o src/languages/retailcrm-es_ES.mo
|
||||
|
||||
install:
|
||||
sudo apt install subversion
|
||||
mkdir -p coverage
|
||||
bash tests/bin/install.sh $(DB_NAME) $(DB_USER) $(DB_HOST) $(DB_PASS) $(WP_VERSION) $(WC_VERSION)
|
||||
|
||||
test:
|
||||
phpunit -c phpunit.xml.dist
|
||||
|
||||
local_test:
|
||||
bash tests/bin/install.sh $(DB_NAME) $(DB_USER) $(DB_HOST) $(DB_PASS) $(WP_VERSION) $(WC_VERSION)
|
||||
phpunit -c phpunit.xml.dist
|
||||
|
||||
run_tests:
|
||||
docker-compose --no-ansi up -d --build mysql
|
||||
docker-compose --no-ansi run --rm --no-deps app make local_test
|
||||
docker-compose down -v
|
||||
|
||||
coverage:
|
||||
wget https://phar.phpunit.de/phpcov-2.0.2.phar && php phpcov-2.0.2.phar merge coverage/ --clover coverage.xml
|
||||
|
||||
build_archive:
|
||||
zip -r $(ARCHIVE_NAME) ./src/*
|
17
README.md
17
README.md
|
@ -1,6 +1,17 @@
|
|||
woocommerce-module
|
||||
[](https://github.com/retailcrm/woocommerce-module/actions)
|
||||
[](https://codecov.io/gh/retailcrm/woocommerce-module)
|
||||
[](https://github.com/retailcrm/woocommerce-module/releases)
|
||||
[](https://php.net/)
|
||||
|
||||
Woocommerce-module
|
||||
==================
|
||||
|
||||
Модуль интеграции с [retailCRM](http://retailcrm.ru)
|
||||
Integration plugin for WooCommerce and [Simla.com](https://www.simla.com)
|
||||
|
||||
Информация о [кастомизации](https://github.com/retailcrm/woocommerce-module/wiki/%D0%9A%D0%B0%D1%81%D1%82%D0%BE%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D0%B0)
|
||||
[Documentation](https://docs.retailcrm.ru/Users/Integration/SiteModules/WooCommerce) page
|
||||
|
||||
[Customization](https://github.com/retailcrm/woocommerce-module/wiki/%D0%9A%D0%B0%D1%81%D1%82%D0%BE%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE-%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D0%B0) info
|
||||
|
||||
#### Local testing
|
||||
|
||||
To local testing run `make run_tests`
|
||||
|
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
4.8.21
|
22
composer.json
Normal file
22
composer.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "retailcrm/woocommerce-retailcrm",
|
||||
"description": "Integration plugin for WooCommerce & RetailCRM",
|
||||
"type": "wordpress-plugin",
|
||||
"authors": [
|
||||
{
|
||||
"name": "RetailDriver LLC",
|
||||
"email": "integration@retailcrm.ru"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"ext-simplexml": "*",
|
||||
"ext-xmlwriter": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"phpunit/phpunit": "7.*",
|
||||
"yoast/phpunit-polyfills": "1.x-dev"
|
||||
}
|
||||
}
|
1550
composer.lock
generated
Normal file
1550
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
45
doc/1.Setup/Abandoned carts settings.md
Normal file
45
doc/1.Setup/Abandoned carts settings.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
### Настройки брошенных корзин
|
||||
|
||||
В версии 4.6.0 добавлен функционал выгрузки брошенных корзин.
|
||||
|
||||
Для активации необходимо включить опцию ***Выгружать брошенные корзины***
|
||||
|
||||
### Брошенные корзины
|
||||
|
||||
Брошенная корзина - клиент заходит на сайт, добавляет/удаляет товары в корзине, а затем завершает визит без оформления заказа.
|
||||
|
||||
> Важно:
|
||||
> * Корзины выгружаются только для зарегестрированных клиентов;
|
||||
> * Для корректной работы корзин, один API ключ = один магизн в CRM;
|
||||
|
||||
При разработке функционала, ориентировались на хуки корзины в WooCommerce:
|
||||
* Хуки для метода **set_cart**:
|
||||
* **woocommerce_add_to_cart** - добавление товара в корзину;
|
||||
* **woocommerce_after_cart_item_quantity_update** - изменение кол-во товара в корзине;
|
||||
* **woocommerce_cart_item_removed** - удаление товара с корзины;
|
||||
* Хуки для метода **clear_cart**:
|
||||
* **woocommerce_cart_emptied** - полная очистка корзины. Также срабатывает при создании заказа;
|
||||
|
||||
Корзина создается в CRM, при первом добавлении товара.
|
||||
|
||||
**Фильтры:**
|
||||
|
||||
> retailcrm_process_cart - позволяет кастомизировать данные корзины.
|
||||
|
||||
**Пример использования:**
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_filter('retailcrm_process_cart', 'process_crm_cart');
|
||||
|
||||
function process_crm_cart($crmCart, $cartItems)
|
||||
{
|
||||
$crmCart['updatedAt'] = null;
|
||||
|
||||
return $crmCart;
|
||||
}
|
||||
```
|
||||
/
|
||||
**Возможные API ошибки:**
|
||||
* WC_Retailcrm_Client_V5::cartGet : Error: [HTTP-code 404] - корзина не найдена в CRM;
|
7
doc/1.Setup/Catalog settings.md
Normal file
7
doc/1.Setup/Catalog settings.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
### Настройки каталога
|
||||
|
||||
В настройке представлены статусы товаров в WooCommerce *(Товары -> карточка товара -> блок Опубликовано)*. Из товаров, чей статус будет соответствовать выбранному, будет сгенерирован ICML-файл каталога. Для выбора необходимо поставить галочку напротив нужного статуса и сохранить настройки.
|
||||
|
||||
Статус видимости товара Личное, также относится к статусам товара "Статус: Опубликовано как личное". Анализ статусов товара был произведен в задаче [#76054](https://redmine.retailcrm.tech/issues/76054)
|
||||
|
||||

|
52
doc/1.Setup/Cron tasks.md
Normal file
52
doc/1.Setup/Cron tasks.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
### Настройки cron задач
|
||||
|
||||
В версии 4.4.5 добавлен функционал для изменения интервалов времени выполнения cron задач.
|
||||
|
||||
Для изменения интервала времени необходимо с помощью фильтра **retailcrm_add_cron_interval** добавить пользовательский интервал. Затем изменить интервал для cron задач с помощью фильтра **retailcrm_cron_schedules**.
|
||||
Кастомизацию необходимо добавить на сервере в директорию wp-content -> mu-plugins -> mu-simla.php. После добавления кастомизации в настройках модуля необходимо очистить старые cron задачи.
|
||||
Перейдите в настройки, откройте "Отладочная информация" и нажмите на кнопку "Очистить cron задачи", появится окно с сообщением об успешной очистке, интервалы будут применены.
|
||||
|
||||
Если необходимо вернуть стандартные интервалы, то удаляем кастомизацию и в настройках так же очищаем старые cron задачи.
|
||||
|
||||
**Интервалы по умолчанию:**
|
||||
```php
|
||||
[
|
||||
'icml' => 'three_hours',
|
||||
'history' => 'five_minutes',
|
||||
'inventories' => 'fiveteen_minutes',
|
||||
'loyalty_upload_price' => 'four_hours'
|
||||
]
|
||||
```
|
||||
> Важно! При использовании фильтра **retailcrm_cron_schedules**, можно использовать ключи: 'icml', 'history', 'inventories'.
|
||||
|
||||
**Фильтры:**
|
||||
|
||||
> retailcrm_add_cron_interval - позволяет добавить пользовательский интервал времени.
|
||||
|
||||
> retailcrm_cron_schedules - позволяет изменить интервал времени для cron задач.
|
||||
|
||||
**Пример использования:**
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_filter('retailcrm_add_cron_interval', 'add_cron_interval');
|
||||
|
||||
function add_cron_interval($schedules)
|
||||
{
|
||||
return ['two_minutes' => [
|
||||
'interval' => 120, // seconds
|
||||
'display' => __('Every 2 minutes')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
add_filter('retailcrm_cron_schedules', 'change_cron_tasks');
|
||||
|
||||
function change_cron_tasks($cronTasks)
|
||||
{
|
||||
$cronTasks['history'] = 'two_minutes';
|
||||
|
||||
return $cronTasks;
|
||||
}
|
||||
```
|
37
doc/1.Setup/Custom fields settings.md
Normal file
37
doc/1.Setup/Custom fields settings.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
### Пользовательские поля
|
||||
|
||||
В настройках модуля есть возможность настроить передачу пользовательских полей из CMS в CRM и обратно. Для этого необходимо настроить соответствие между мета полями CMS и пользовательскими полями CRM.
|
||||
|
||||
Слева располагаются мета поля CMS, справа пользовательские поля CRM. В выпадающем списке слева нужно выбрать мета поля CMS, в выпадающем списке справа нужно выбрать пользовательское поле CRM и таким образом сопоставить их.
|
||||
|
||||
Пользовательские поля разбиты на две группы для заказов "Custom fields for order" и для клиентов "Custom fields for customer".
|
||||
|
||||
При нажатии на кнопку "Custom fields for order/customer" будет добавлены новые списки для выбора. Количество списков не может быть больше количества мета полей CMS или пользовательских полей CRM. Поэтому когда добавили максимальное число списков, кнопка добавления пропадает. Для каждой группы предусмотрена своя кнопка добавления.
|
||||
|
||||
Обратите внимание, что значения не должны повторяться, т.е. для двух мета полей CMS, не может быть выбрана одно и то же пользовательское поле CRM. Необходимо выбирать 1 к 1. Если вдруг выбрать значение которое уже используется, будет показано предупреждающее уведомление, выбранный список начнет мигать красным и в этом списке будет установленно значение по умолчанию "Select value".
|
||||
|
||||
Сопоставление можно удалить, если нажать на крестик справа от списка пользовательский полей CRM.
|
||||
|
||||
Для сохранения выбранных сопоставлений необходимо нажать на кнопку "Сохранить изменения" в конце страницы настроек.
|
||||
|
||||
Также в списке значений для сопоставления выводятся **только Активные в CRM пользовательские поля**.
|
||||
|
||||
На стороне CRM доступные пользовательские поля можно увидеть перейдя в **Настройки - Системные - Пользовательские поля.**
|
||||
|
||||
|
||||
В версии 4.6.5 добавлен функционал для передачи мета полей CMS в некоторые стандартные поля CRM.
|
||||
|
||||
Список стандартных полей CRM доступных для передачи данных:
|
||||
* Имя
|
||||
* Фамилия
|
||||
* Телефон
|
||||
* E-mail
|
||||
* Адрес
|
||||
* Город
|
||||
* Индекс
|
||||
* Регион
|
||||
* Теги (доступно только для клиентов)
|
||||
* Комментарий клиента (доступно только для заказов)
|
||||
* Комментарий менеджера (доступно только для заказов)
|
||||
|
||||
**Важно! Передача мета полей WC в стандартные поля CRM реализована только из CMS в CRM, обратная синхронизация не предусмотрена.**
|
16
doc/1.Setup/Debug information.md
Normal file
16
doc/1.Setup/Debug information.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
### Режим отладки
|
||||
|
||||
Служит для включения расширенного логирования, предназначен для разработчиков. Для просмотра логов необходимо перейти *WooCommerce -> Статус -> Журналы* и там смотреть на файлы **retailcrm_debug**.
|
||||
|
||||
### Отладочная информация
|
||||
|
||||
Вывод основной информации о работе модуля. На данный момент выводит время последнего запуска крон-задач:
|
||||
<br/><br/>**History** - показывает время и дату запуска обновления по истории *(данные будут отображены, если активна опция “Загрузка данных из CRM”)*.
|
||||
|
||||
**Icml** - показывает время и дату, когда последний раз срабатывала команда генерации каталога по WP-CRON *(информация будет отображена, если активна опция “Генерация ICML каталога товаров с помощью wp-cron”)*.
|
||||
|
||||
**Inventories** - показывает время и дату запуска получения остатков по товарам из CRM в WooCommerce *(данные будут отображены, если активна опция “Настройка управления остатками”)*.
|
||||
|
||||
Добавлено в версии 4.3.3.
|
||||
|
||||

|
24
doc/1.Setup/Delivery methods.md
Normal file
24
doc/1.Setup/Delivery methods.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
### Способы доставки
|
||||
|
||||
Необходимо настроить соответствие между доставками WooCommerce и CRM.
|
||||
|
||||
Слева располагаются доставки WooCommerce, справа CRM. В выпадающем списке справа нужно выбрать справочник доставки в CRM, который будет соответствовать доставке в WooCommerce. Данное сопоставление требуется **выбрать для всех доставок**. Если ни одно из предложенных значений не подходит, то необходимо создать в CRM новый справочник доставки, либо пропустить сопоставление.
|
||||
|
||||
После создания новой доставки *(справочника)* на стороне CRM, в WooCommerce необходимо обновить страницу настроек *(перед этим не забудьте сохранить указанные ранее настройки)*, чтобы в предложенных значениях появился новый созданный справочник.
|
||||
|
||||
Если сопоставление пропустить и не выбрать ни одного значения, в этом случае, если в заказе в WooCommerce будет выбрана данная доставка, то в CRM в заказе “Тип доставки” **будет не указан**.
|
||||
|
||||
Обратите внимание, что значения не должны повторяться, т.е. для двух доставок WooCommerce, не может быть выбрана одна и та же доставка *(справочник доставки)* в CRM. Необходимо выбирать 1 к 1.
|
||||
|
||||
**1 доставка WooCommerce = 1 доставка CRM**. Иначе будут наблюдаться ошибки в работе модуля.
|
||||
|
||||
Также в списке значений для сопоставления выводятся **только Активные в CRM справочники**.
|
||||
|
||||
Доставки на стороне WooCommerce можно посмотреть перейдя в *WooCommerce - Настройки - вкладка Доставка - Зоны доставки*; Для каждой зоны доставки будет перечень своих транспортных компаний.
|
||||
|
||||

|
||||
|
||||
|
||||
На стороне CRM доступные доставки можно увидеть перейдя в *Настройки - Справочники - Типы доставок.*
|
||||
|
||||

|
57
doc/1.Setup/Generating ICML catalog.md
Normal file
57
doc/1.Setup/Generating ICML catalog.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
### Генерация ICML
|
||||
|
||||
Данная опция позволяет сгенерировать каталог вручную.
|
||||
|
||||
Для активации необходимо нажать на кнопку и дождаться сообщения о завершении генерации. Сгенерированный каталог будет доступен по ссылке https://yoursite.com/simla.xml
|
||||
|
||||
Чтобы проверить, что команда по генерации каталога отработала успешно, можно перейти по ссылке и сравнить дату генерации каталога с текущей:
|
||||
|
||||

|
||||
|
||||
Ссылку на каталог требуется указать в настройках магазина в CRM. Для его загрузки в CRM необходимо активировать опцию “Загрузить каталог из ICML сейчас” и сохранить настройки.
|
||||

|
||||
|
||||
### Генерация ICML каталога товаров с помощью wp-cron
|
||||
|
||||
Данный функционал позволяет генерировать каталог в автоматическом режиме с помощью WP-CRON, в этом случае не потребуется каждый раз при необходимости обновить каталог, заходить в настройки и запускать его загрузку вручную.
|
||||
|
||||
Каталог генерируется **раз в 3 часа**.
|
||||
|
||||
### Синхронизация остатков и связь товаров
|
||||
|
||||
Функционал служит для **идентификации товаров по SKU (xmlID)**. Для активации необходимо поставить галочку.
|
||||
|
||||
После активации опции при генерации каталога в нем появится параметр xmlID *(артикул)*.
|
||||
|
||||
С версии 4.7.5 после активации/деактивации опции и сохранении настроек, каталог будет сгенерирован автоматически.
|
||||
|
||||
**xmlID** - внешний идентификатор товара, элемент не является обязательным. В случае, если интернет-магазин использует выгрузку номенклатуры товаров из складской системы *(1С, МойСклад)*, то значение этого элемента соответствует идентификатору товара в данной системе. Активируется, когда клиенты используют Woocommerce + MC/1C.
|
||||
|
||||
### Недавние обновления:
|
||||
|
||||
**В версии 4.4.4** добавлен функционал передачи описания товара в каталог. В настройках каталога необходимо выбрать, какое описание передавать краткое или полное. По умолчанию передается полное описание товара.
|
||||
|
||||
Поле description(описание) выводится в карточке товара, так же его можно использовать в twig-шаблонах. Например, для вывода в печатных формах.
|
||||
Пример получения описание торгового предложения:
|
||||
```twig
|
||||
{% for availableOrderProduct in order.availableOrderProducts %} {{ availableOrderProduct.getOffer().getDescription() }} {% endfor %}
|
||||
```
|
||||
|
||||
**В версии 4.4.6** добавлен фильтр:
|
||||
|
||||
> retailcrm_process_offer - позволяет изменить данные товара, перед записью в ICML каталог.
|
||||
|
||||
|
||||
**Пример использования:**
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_action('retailcrm_process_offer', 'changeProductInfo', 10, 2);
|
||||
|
||||
function changeProductInfo($productData, $wcProduct)
|
||||
{
|
||||
$productData['name'] .= 'Test';
|
||||
|
||||
return $productData;
|
||||
}
|
||||
```
|
7
doc/1.Setup/Loading data from CRM.md
Normal file
7
doc/1.Setup/Loading data from CRM.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
### Загрузка данных из Simla.com
|
||||
|
||||
Функционал необходим для выгрузки заказов и изменений по ним, изменений по существующим клиентам из CRM в WooCommerce.
|
||||
|
||||
**Новые клиенты *(без заказов)*** из CRM в WooCommerce **не выгружаются**.
|
||||
|
||||
История грузится **раз в 5 минут**. Для активации необходимо поставить галочку.
|
21
doc/1.Setup/Loading orders.md
Normal file
21
doc/1.Setup/Loading orders.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
### Настройки выгрузки
|
||||
|
||||
Данный функционал позволяет выгрузить архивные заказы и клиентов из WooCommerce в CRM.
|
||||
|
||||
Для активации нужно нажать “Выгрузить”, появится progress bar, отображающий процесс выгрузки заказов. После окончания выгрузки, должно появиться сообщение об успешном окончании. В момент выгрузки заказов, **страницу браузера закрывать нельзя**.
|
||||
|
||||
Ранее, модуль мог выгрузить не более 700-800 заказов, после реализации задачи [#70113](https://redmine.retailcrm.tech/issues/70113), механика была изменена, сейчас **выгрузка происходит пошагово**, шаг равен 50 элементам *(заказам/клиентам)*.
|
||||
|
||||
Если при выгрузке возникнут проблемы с каким-либо заказом/клиентом, то он будет пропущен и выгрузка пойдет дальше. Информацию по заказу/клиенту, с которым возникли проблемы при выгрузке, можно получить только включив расширенное логирование.
|
||||
|
||||
### Выгрузка заказов по идентификаторам
|
||||
|
||||
Данный функционал позволяет выгружать заказы в CRM, если они по каким-либо причинам не выгрузились автоматически. Рекомендуется выгружать таким способом, **не более 50 заказов**.
|
||||
|
||||
Для выгрузки заказов в CRM требуется ввести в поле “Идентификаторы заказов” ID заказов и нажать на кнопку “Выгрузить”. После выгрузки должно появится сообщение об успешном завершении выгрузки.
|
||||
|
||||
Идентификаторы выгружаемых заказов требуется **разделять запятой**.
|
||||
|
||||
**ID заказа = номеру заказа**.
|
||||
|
||||

|
15
doc/1.Setup/Main Settings.md
Normal file
15
doc/1.Setup/Main Settings.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
### API URL
|
||||
|
||||
Требуется ввести полный адрес системы (https://test.retailcrm.ru). Для ввода в поле можно использовать как адрес RetailCRM, так и Simla.com.
|
||||
|
||||
### API ключ
|
||||
|
||||
Ключ должен иметь доступ только к одному магазину (**1 магазин = 1 ключ**). Также для ключа должны быть разрешены все методы API *(рекомендуется)* в настройках системы.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
***При первичной настройке модуля, дальнейшие опции отображаются после корректного ввода API URL и API ключа!***
|
5
doc/1.Setup/Methods order registration.md
Normal file
5
doc/1.Setup/Methods order registration.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
### Способы оформления заказа
|
||||
|
||||
Опция предоставляет выбор, заказы с какими способами оформления, перечисленными в CRM, будут выгружаться из CRM в WooCommerce. Для активации необходимо выбрать нужные способы и сохранить настройки. *Чтобы выбрать только некоторые из способов, нужно зажать Ctrl и выбрать левой кнопкой мыши необходимые способы.*
|
||||
|
||||
*Обратите внимание, в опции перечислены только те способы оформления заказа, которые Активны в CRM.*
|
34
doc/1.Setup/Order statuses.md
Normal file
34
doc/1.Setup/Order statuses.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
### Статусы
|
||||
|
||||
В данной опции необходимо настроить соответствие между статусами WooCommerce и CRM.
|
||||
|
||||
Слева располагаются статусы WooCommerce, справа CRM. В выпадающем списке справа нужно выбрать статус в CRM, который будет соответствовать статусу в WooCommerce. Данное сопоставление требуется выбрать **для всех статусов**. Если ни одно из предложенных значений **не подходит**, с версии 4.4.1 вы можете выбрать "Не отправлять в CRM", либо **создать в CRM новый статус**
|
||||
|
||||
После создания нового статуса на стороне CRM, в WooCommerce необходимо обновить страницу настроек *(перед этим не забудьте сохранить указанные ранее настройки)*, чтобы в предложенных значениях появился новый созданный статус.
|
||||
|
||||
Также в списке значений для сопоставления выводятся только **Активные в CRM статусы**.
|
||||
|
||||
Обратите внимание, что **значения не должны повторяться**, т.е. для двух статусов WooCommerce, не может быть выбран один и тот же статус в CRM. Необходимо выбирать 1 к 1.
|
||||
|
||||
**1 статус в WooCommerce = 1 статус в CRM**. Иначе будут наблюдаться ошибки в работе модуля.
|
||||
|
||||
По умолчанию в WooCommerce присутствуют **7 стандартных статусов**:
|
||||
|
||||
**В ожидании оплаты** – заказ получен *(не оплачен)*.
|
||||
|
||||
**Не удался** – платеж не удался или был отклонен *(неоплаченный)*.
|
||||
|
||||
**Обработка** – оплата получена, а остатки по товарам были уменьшены – заказ ожидает выполнения. Все заказы товаров требуют обработки, кроме заказов цифровых и загружаемых товаров.
|
||||
|
||||
**Выполнен** – заказ выполнен и завершен – не требует дальнейших действий.
|
||||
|
||||
**На удержании** – ожидается платеж – остатки по товару уменьшены, но необходимо подтвердить оплату. В данном статусе нет возможности вернуть остаток по товару в WooCommerce.
|
||||
|
||||
**Отменен** – заказ отменен – не требует дальнейших действий. При указании данного статуса заказу, остаток по товару, который был списан, автоматически возвращается.
|
||||
|
||||
**Возвращен** – возврат по заказу - в данном статусе есть возможность вернуть остаток по товару, но для этого требуется перейти в WooCommerce и явно указать флаг возврата и сумму по товару, которая будет возвращена. При установлении в CRM статуса, соответствующего статусу возврата в WooCommerce, списанные остатки по товару **не вернутся автоматически в запас**. Для того, чтобы их вернуть, требуется перейти в админ панель WooCommerce, зайти в заказ в блоке с перечнем товаров, нажать “Возврат”
|
||||
Возвращен – возврат по заказу - в данном статусе есть возможность вернуть остаток по товару, но для этого требуется перейти в WooCommerce и явно указать флаг возврата и сумму по товару, которая будет возвращена. При установлении в CRM статуса, соответствующего статусу возврата в Woo, списанные остатки по товару не вернутся автоматически в запас. Для того, чтобы их вернуть, требуется перейти в админ панель Woo, зайти в заказ в блоке с перечнем товаров, нажать “Возврат”
|
||||
|
||||

|
||||
|
||||

|
25
doc/1.Setup/Payment methods.md
Normal file
25
doc/1.Setup/Payment methods.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
### Способы оплаты
|
||||
|
||||
Необходимо настроить соответствие между оплатами WooCommerce и CRM.
|
||||
|
||||
Слева располагаются оплаты WooCommerce, справа CRM. В выпадающем списке справа нужно выбрать справочник оплаты в CRM, который будет соответствовать оплате в WooCommerce. Данное сопоставление требуется выбрать **для всех оплат**. Если ни одно из предложенных значений не подходит, то необходимо создать в CRM новый справочник оплаты, либо пропустить сопоставление.
|
||||
|
||||
После создания нового типа оплаты *(справочника)* на стороне CRM, в WooCommerce необходимо обновить страницу настроек *(перед этим не забудьте сохранить указанные ранее настройки)*, чтобы в предложенных значениях появился новый созданный справочник.
|
||||
|
||||
Если сопоставление пропустить и не выбрать ни одного значения, в этом случае, если в заказе в WooCommerce будет выбрана данная оплата, то в CRM в заказе “Тип оплаты” **будет не указан**.
|
||||
|
||||
Обратите внимание, что значения не должны повторяться, т.е. для двух оплат WooCommerce, не может быть выбрана одна и та же оплата *(справочник оплаты)* в CRM. Необходимо выбирать 1 к 1.
|
||||
|
||||
**1 оплата WooCommerce = 1 оплата CRM**. Иначе будут наблюдаться ошибки в работе модуля.
|
||||
|
||||
Также в списке значений для сопоставления выводятся *только Активные в CRM справочники*.
|
||||
|
||||
Перечень оплат в WooCommerce можно посмотреть перейдя в *WooCommerce - Настройки - вкладка Платежи*
|
||||
|
||||

|
||||
|
||||
Обратите внимание, что в настройках сопоставления, перечень существующих оплат в WooCommerce выводится *вне зависимости от их активности*.
|
||||
|
||||
На стороне CRM доступные доставки можно увидеть перейдя в *Настройки - Справочники - Типы оплат*
|
||||
|
||||

|
9
doc/1.Setup/Setting WhatsApp.md
Normal file
9
doc/1.Setup/Setting WhatsApp.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
### Настройки WhatsApp
|
||||
|
||||
Данная опция позволяет отображать на сайте иконку WhatsApp, при нажатии на иконку будет открываться чат с пользователем, номер телефона которого указан в настройке.
|
||||
|
||||
Для активации необходимо поставить галочку “Активировать WhatsApp”, если необходимо отображать иконку справа, поставить галочку “Разместить в правом нижнем углу сайта” по умолчанию располагается в левом нижнем углу.
|
||||
|
||||
В поле “Введите номер телефона” требуется ввести корректный номер телефона. *Для РФ номер требуется вводить с 7, если номер будет введен начиная с 8, при открытии WhatsApp будет выведена ошибка.*
|
||||
|
||||
Опция добавлена в версии 4.3.0.
|
30
doc/1.Setup/Setting stock.md
Normal file
30
doc/1.Setup/Setting stock.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
### Настройка управления остатками
|
||||
|
||||
Данная опция позволяет управлять остатками на стороне CRM. Для активации необходимо поставить галочку. Информация по остаткам из CRM в WooCommerce обновляется **раз в 15 минут**.
|
||||
|
||||
Обратите внимание, для того чтобы вести остатки в CRM, требуется в настройках системы указать магазину "Вести каталог в системе", присвоить ему склад и включить редактирование остатков, а на стороне WooCommerce в настройках модуля активировать опцию "Синхронизация остатков".
|
||||
|
||||
Анализ работы по списанию остатков был произведен в задаче [#75178](https://redmine.retailcrm.tech/issues/75178).
|
||||
|
||||
**В версии 4.6.8** появилась возможность указать, с каких складов CRM выгружать остатки для торговых предложений. В блоке "Настройка управления остатками" добавлен мультисписок с доступными складами CRM. Для выбора нескольких складов зажмите CTRL (для Windows и Linux) или ⌘ Command (для MacOS).
|
||||
|
||||
Если в CRM несколько складов, а в настройках не выбрали склады для выгрузки, в таком случае остатки будут передаваться по всем складам.
|
||||
|
||||
#### На стороне WooCommerce
|
||||
Если остатки **ведут на сайте**, то в этом случае, при оформлении заказа на сайте, остаток по товару сразу же списывается в WooCommerce. В CRM обновление остатков по товарам происходит в момент синхронизации ICML-файла каталога *(~ 4 часа)*.
|
||||
|
||||
| Кейс | Результат |
|
||||
|----------|-------------|
|
||||
| Оформить заказ на сайте, после указать статус Отменен заказу на стороне WooCommerce | При оформлении заказа остатки сразу списываются, как в WooCommerce, так и в CRM; <br/> После указания отмены заказу, в WooCommerce остатки сразу возвращаются, в CRM, если настроена автоматическая отмена товара при отмене заказа, остатки возвращаются, иначе нужно ждать обновления каталога |
|
||||
| Оформить заказ в CRM, после указать статус из группы Выполнен в CRM | В CRM по товарам остатки не списываются; <br/> В WooCommerce остаток по товару списывается, когда приходит статус Выполнен |
|
||||
| Оформить заказ в CRM, после указать статус из группы Отмена в CRM | В CRM остатки не списываются *(в т.ч. и при установлении отмены статусу товара)*, в WooCommerce остатки по товару также не возвращаются |
|
||||
|
||||
#### На стороне CRM
|
||||
При ведении стока **на стороне CRM**, остаток по товару сразу списывается в CRM. На стороне WooCommerce остатки по товарам обновляются **раз в 15 минут**.
|
||||
Если данный статус в CRM не выставляет автоматически статус отмены товару, то в CRM остатки не возвращаются, также, как и на стороне WooCommerce <br/> Если будет выставлен статус отмены товару, остатки возвратятся в CRM и спустя 15 минут в WooCommerce
|
||||
|
||||
| Кейс | Результат |
|
||||
|------------------------------------------------------------------------------|-----------|
|
||||
| Оформить заказ в CRM, после указать в CRM **статус товару** из группы Отмена | В CRM остаток по товару возвращается, в WooCommerce сток обновляется через 15 минут |
|
||||
| Оформить заказ на сайте в WooCommerce | Заказ выгружается в CRM, по нему списываются остатки по товару, данное списание передается на сторону WooCommerce |
|
||||
| Оформить заказ на сайте в WooCommerce, после указать статус Отменен в админке сайта WooCommerce | В CRM приходит статус отмены заказа, если в CRM настроена опция автоматической отмены товара при отмене заказа, то остатки возвращаются в CRM, спустя время и в WooCommerce |
|
7
doc/1.Setup/Settings order number.md
Normal file
7
doc/1.Setup/Settings order number.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
### Передача номера заказа
|
||||
|
||||
Функционал позволяет в номер заказа CRM передавать номер заказа WooCommerce.
|
||||
|
||||
Если опция не активна, то номер, с которым выгрузится заказ из WooCommerce в CRM, будет сгенерирован с использованием “Шаблона генерации номера заказа из API” *(в CRM Настройки - Системные - Заказы)*.
|
||||
|
||||

|
3
doc/1.Setup/Settings.md
Normal file
3
doc/1.Setup/Settings.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Settings
|
||||
|
3
doc/1.Setup/Updating data in CRM.md
Normal file
3
doc/1.Setup/Updating data in CRM.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
### Обновление данных в Simla.com
|
||||
|
||||
Если активировать данную опцию, **любые изменения заказов из Woocommerce не будут передаваться в CRM**. Изменения заказа из CRM в Woocomerce передаются.
|
33
doc/1.Setup/User roles.md
Normal file
33
doc/1.Setup/User roles.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# User roles
|
||||
|
||||
С версии `4.3.7` опция `"Роли клиентов"` была удалена из настроек модуля. Сейчас выгружаются пользователи со всеми доступными ролями в CMS.
|
||||
Добавили фильтр `retailcrm_customer_roles` для корректировки выгружаемых пользователей.
|
||||
|
||||
## Пример работы фильтра
|
||||
В приведенном ниже примере показано, как возможно корректировать роли выгружаемых пользователей:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_filter('retailcrm_customer_roles', 'editCustomerRoles', 10, 1);
|
||||
|
||||
function editCustomerRoles($roles)
|
||||
{
|
||||
if (isset($roles['customer'])) {
|
||||
unset($roles['customer']);
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
```
|
||||
|
||||
## Описание работы функционала
|
||||
У каждого зарегистрированного пользователя в WP есть роль. Роль отображает права пользователя на сайте. Ниже приведены основные кейсы работы с пользователями:
|
||||
|
||||
* Пользователь "гость (клиент без регистрации)" создает заказ. Заказ корректно выгрузился в CRM, клиент был создан без externalId. Если этот "гость" создаст еще один заказ, то заказ корректно выгрузится и установится связь с этим клиентом (будет произведен поиск по email).
|
||||
* Создали пользователя, например с ролью "Подписчик", он корректно выгрузился в CRM, есть externalId и данные по клиенту, только те, что указаны при создании в админке WP. Данный пользователь оформляет заказ, заказ корректно выгрузился в CRM и связался с клиентом, клиенту добавилась данные: телефон, адрес. Обновление данных пользователя происходит корректно (будет произведен поиск по externalId).
|
||||
* Функционал корректно работает и для корпоративных клиентов.
|
||||
* Запустили архивную выгрузку, данные выгрузились корректно, все заказы, все клиенты без дублей.
|
||||
* Запретили через фильтр передавать пользователей с ролью "Клиент", такие пользователи в CRM выгружаться не будут.
|
||||
* Создали заказ для пользователя с ролью "Клиент", заказ выгрузился в CRM, так же создался клиент без externalId, модуль воспринимает таких клиентов как "гостей". Данное поведение считается корректным тк мы не выгружаем пользователей с определенной ролью, но они создаются с заказов (заказы мы передаем).
|
||||
|
53
doc/1.Setup/Сorporate сlients.md
Normal file
53
doc/1.Setup/Сorporate сlients.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
### Поддержка корпоративных клиентов
|
||||
|
||||
Для включения функционала корпоративных клиентов, необходимо поставить галочку в настройках модуля напротив опции **Поддержка корпоративных клиентов**. Также в CRM должен быть активен функционал корпоративных клиентов.
|
||||
|
||||
В WooCommerce клиент считается корпоративным, если заполнено поле **Компания** в Платежном адресе *(Billing address)* в профиле клиента.
|
||||

|
||||
|
||||
При регистрации покупателя с необходимыми данными, в CRM будет выгружен новый клиент, как обычное физ лицо, однако, если этот клиент оформит заказ, то в CRM будет создан корп клиент с наименованием из поля *Компания*, а клиент станет *Контактным лицом* данного корп клиента, тип клиента также изменится на *Контактное лицо*.
|
||||
|
||||
В карточке корп клиента указаны:
|
||||
- Наименование,
|
||||
- Магазин,
|
||||
- Дата регистрации. <br/><br/>
|
||||
*__В блоке Контактные лица__*:
|
||||
- ФИО клиента,
|
||||
- Email,
|
||||
- Email-подписка *(значение зависит от флага подписки на рассылку на сайте)*,
|
||||
- признак Основное,
|
||||
- Телефон,
|
||||
- нет привязки к Компании. <br/><br/>
|
||||
*__В блоке Компании__*:
|
||||
- Название,
|
||||
- Статус,
|
||||
- признак Основная,
|
||||
- Тип контрагента (Юр лицо),
|
||||
- Адрес регистрации *(из Платежного адреса)*,
|
||||
- есть привязка к адресу. <br/><br/>
|
||||
*__В блоке Адреса__*
|
||||
- указан Платежный адрес *(заполнены все строки)*.
|
||||
|
||||
В карточку контактного лица *(клиента, на которого оформлен заказ)* в данные адреса выгружается **Платежный адрес**.
|
||||
|
||||
#### На стороне WooCommerce
|
||||
| # | Кейс *(действие на стороне Woo)* | Результат |
|
||||
|--|--|--|
|
||||
|1| Зарегистрировать клиента, указать в профиле Billing address | При добавлении Billing address в профиле клиента в Woo, он выгружается в CRM в карточку клиента |
|
||||
|2| Зарегистрировать клиента, указать в профиле Shipping address | Shipping address не выгружается в CRM в карточку клиента |
|
||||
|3| Зарегистрировать клиента, указать в профиле Billing и Shipping address | В карточку клиента в CRM выгружается только Billing address |
|
||||
|4| Изменить в профиле клиента Billing address | В карточке клиента в CRM обновляется адрес |
|
||||
|5| Изменить в профиле клиента Shipping address | Shipping address не выгружается в карточку клиента в CRM |
|
||||
|6| Зарегистрировать клиента, указать в профиле Billing address, после удалить адрес | При указании Billing address в профиле клиента в Woo, он выгружается в карточку клиента в CRM <br/> Адрес в Woo можно изменить, но удалить нельзя *(возможность отсутствует)* |
|
||||
|7| Зарегистрировать клиента, указать в профиле Shipping address, после удалить | При указании Shipping address в профиле клиента в Woo, он не выгружается в карточку клиента в CRM <br/> Адрес в Woo можно изменить, но удалить нельзя (возможность отсутствует) |
|
||||
|8| Оформить заказ с указанием только Billing address | В карточке клиента в CRM указан Billing address <br/> В карточке заказа в CRM указан Billing address |
|
||||
|9| Оформить заказ с указанием Billing и Shipping address | В карточке клиента в CRM указан Billing address <br/> В карточке заказа в CRM в блоке Доставка указан Shipping address |
|
||||
| | | |
|
||||
|10| Зарегистрировать клиента, указать при оформлении заказа значение в поле Компания в Billing address | При указании компании в Billing address в профиле клиента, **корп клиент не создается** <br/> **Корп клиент создается при оформлении заказа** <br/> **billing = shipping** <br/> В карточке заказа в CRM в блоке Доставка указан Billing address <br/> В карточке корп клиента в CRM в блоке Компании в поле "Адрес регистрации" указан Billing address; в блоке Адреса также указан Billing address <br/> В карточке Контактного лица *(клиента, на которого оформлен заказ)* в CRM указан Billing address |
|
||||
|11| Указать при оформлении заказа значение в поле Компания в Shipping address (в Billing address не указано значение в поле Компания) | Корп клиент не создается в CRM; В карточке заказа в CRM указан Shipping address <br/> В карточке клиента в CRM указан Billing address |
|
||||
|12| Указать при оформлении заказа значение в поле Компания в Billing и Shipping address | Корп клиент создан <br/> В карточке заказа в CRM указан Shipping address <br/> В карточке корп клиента в CRM в блоке Компании в поле Адрес регистрации указан Billing address; в блоке Адреса также указан Billing address <br/> В карточке Контактного лица в CRM указан Billing address |
|
||||
|13| Изменить значение в поле Компания в профиле клиента в Billing адресе, оформить заказ | Создается новый корп клиент в CRM |
|
||||
|14| Изменить значение в поле Компания в профиле клиента в Shipping адресе | Никаких изменений на стороне CRM нет |
|
||||
|15| Зарегистрировать нового клиента, в Billing address в поле Компания указать название существующего корп клиента в CRM | Новый корп клиент не создается в CRM, добавляется новое Контактное лицо в карточку корп клиента + добавляется Billing address в блок Адреса, если он отличен от существующего |
|
||||
|16| Зарегистрировать нового клиента, в Billing address в поле Компания указать название существующего корп клиента в CRM и существующий адрес | Адрес не дублируется |
|
||||
| | | |
|
17
doc/2. Workflow/ Client Registration.md
Normal file
17
doc/2. Workflow/ Client Registration.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
#Регистрация клиентов
|
||||
|
||||
Клиент может зарегистрироваться на сайте:
|
||||
* Администратор может зарегистрировать клиента через административную панель WordPress.
|
||||
* На странице wp-admin.
|
||||
* При заполнении email в форме регистрации на сайте.
|
||||
* Когда оформляет новый заказ, если клиент является "гостем" ему будет предложено зарегистрироваться.
|
||||
|
||||
Модуль обрабатывает регистрацию клиентов через хук **user_register**.
|
||||
|
||||
Модуль обрабатывает таких клиентов по следующей логике:
|
||||
1. Производится поиск по email в CRM, если клиент найден, происходит обновление его данных в CRM и актуализируется его externalId.
|
||||
2. Если клиент не найден в CRM, будет создан новый.
|
||||
|
||||
Данная логика позволяет минимизировать количество дублей.
|
||||
Возможно ситуация, когда клиент был удален в CMS, но в CRM такой клиент продолжает существовать, чтобы не потерять таких клиентов производится актуализация их данных.
|
||||
|
28
doc/2. Workflow/Address.md
Normal file
28
doc/2. Workflow/Address.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Работа с адресами
|
||||
|
||||
С версии `4.3.8` изменена логика работы с адресами.
|
||||
|
||||
В заказ CRM c заказа WooCommerce будет передаваться, только shipping адрес. Если при оформлении заказа указан только billing адрес, то WooCommerce записывает в БД эти же данные в shipping, то есть shipping = billing.
|
||||
|
||||
При создании обычных/корпоративных клиентов в CRM будет передаваться billing адрес с заказа/пользователя WooCommerce. Если клиент гость и у него нет данных по billing адресу, тогда будет передан billing адрес заказа.
|
||||
|
||||
Для кастомизаций адресов в CRM, добавили новые фильтры:
|
||||
* `retailcrm_process_order_address`
|
||||
* `retailcrm_process_customer_address`
|
||||
* `retailcrm_process_customer_corporate_address`
|
||||
|
||||
## Пример работы фильтров
|
||||
В приведенном ниже примере показано, как возможно кастомизировать адрес заказа:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_filter('retailcrm_process_order_address', 'editOrderAddress', 10, 2);
|
||||
|
||||
function editOrderAddress($address, $order)
|
||||
{
|
||||
$address['text'] = 'Test';
|
||||
|
||||
return $address;
|
||||
}
|
||||
```
|
10
doc/2. Workflow/Order statuses.md
Normal file
10
doc/2. Workflow/Order statuses.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
## Статусы заказов
|
||||
|
||||
Обратная синхронизация (CRM --> WC):
|
||||
* Если в CRM создают заказ со статусом для которого не выбран маппинг, модуль по умолчанию поставит "pending" для этого заказа в WC.
|
||||
* Если в CRM изменении заказ со статусом для которого не выбран маппинг, статус заказа в WC изменен не будет.
|
||||
|
||||
Прямая синхронизация (WC --> CRM):
|
||||
* Если в WC создают/изменяют заказ со статусом для которого в настройках маппинга статусов выбрано "Не отправлять в CRM", при создании заказа, в массиве данных заказа поля "status" не будет, CRM поставит статус по умолчанию, при изменении статус заказа в CRM изменен не будет.
|
||||
|
||||
|
63
doc/3. Customization/Filters.md
Normal file
63
doc/3. Customization/Filters.md
Normal file
|
@ -0,0 +1,63 @@
|
|||
### Фильтры
|
||||
|
||||
Если вы хотите изменить данные отправляемые между CRM и CMS, вы можете использовать **пользовательские фильтры**.
|
||||
|
||||
Чтобы использовать фильтры, необходимо в директории wp-content создать директорию mu-plugins и в ней создать кастомный файл mu-simla.php.
|
||||
|
||||
### Список доступных фильтров
|
||||
|
||||
> retailcrm_process_customer - позволяет изменить данные клиента при передачи из CMS -> CRM.
|
||||
|
||||
> retailcrm_process_customer_address - позволяет изменить адрес клиента при передачи из CMS -> CRM.
|
||||
|
||||
> retailcrm_process_customer_corporate - позволяет изменить данные корпоративного клиента при передачи из CMS -> CRM.
|
||||
|
||||
> retailcrm_process_customer_corporate_address - позволяет изменить адрес корпоративного клиента при передачи из CMS -> CRM.
|
||||
|
||||
> retailcrm_process_customer_corporate_company - позволяет изменить компанию корпоративного клиента при передачи из CMS -> CRM.
|
||||
|
||||
> retailcrm_customer_roles - позволяет изменить допустимые роли клиентов.
|
||||
|
||||
> retailcrm_daemon_collector - позволяет изменить данные для Daemon Collector.
|
||||
|
||||
> retailcrm_initialize_analytics - позволяет изменить данные скрипта для Google Analytics.
|
||||
|
||||
> retailcrm_send_analytics - позволяет изменить отправляемые данные Google Analytics.
|
||||
|
||||
> retailcrm_process_customer_custom_fields - позволяет изменить данные кастомных полей клиента при передачи из CRM -> CMS .
|
||||
|
||||
> retailcrm_history_before_save - позволяет изменить данные заказа и клиента при передачи из CRM -> CMS.
|
||||
|
||||
> retailcrm_process_order_custom_fields - позволяет изменить данные кастомных полей заказ при передачи из CRM -> CMS.
|
||||
|
||||
> retailcrm_process_offer - позволяет изменить данные товара перед записью в ICML каталог.
|
||||
|
||||
> retailcrm_process_order - позволяет изменить данные заказа при передачи из CMS -> CRM.
|
||||
|
||||
> retailcrm_process_order_address - позволяет изменить адрес заказа при передачи из CMS -> CRM.
|
||||
|
||||
> retailcrm_add_cron_interval - позволяет добавить пользовательский интервал времени.
|
||||
|
||||
> retailcrm_cron_schedules - позволяет изменить интервал времени для cron задач.
|
||||
|
||||
> retailcrm_shipping_list - позволяет изменить методы доставки с CMS.
|
||||
|
||||
> retailcrm_order_create_after - позволяет проверить создание заказа и произвести дополнительные действия
|
||||
|
||||
> retailcrm_order_update_after - позволяет проверить изменение заказа и произвести дополнительные действия
|
||||
|
||||
> retailcrm_change_default_meta_fields - позволяет изменить список получаемых по умолчанию мета-полей
|
||||
|
||||
**Пример использования:**
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_action('retailcrm_process_offer', 'changeProductInfo', 10, 2);
|
||||
|
||||
function changeProductInfo($productData, $wcProduct)
|
||||
{
|
||||
$productData['name'] .= 'Test';
|
||||
|
||||
return $productData;
|
||||
}
|
||||
```
|
4
doc/4. Known issues/Issues.md
Normal file
4
doc/4. Known issues/Issues.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
## Платежи
|
||||
|
||||
1. Модуль при обратной синхронизации устанавливает только метод оплаты, но никак не реагирует на статус оплат в WooCommerce.
|
||||
|
161
doc/FAQ/FAQ.md
Normal file
161
doc/FAQ/FAQ.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
## FAQ
|
||||
|
||||
**Ребрендинг модуля с RetailCRM на Simla.com**
|
||||
|
||||
Ребрендинг модуля WooCommerce связан с тем, что данные он востребован на зарубежном направлении и название нашей системы в Испании и ЛатАм - Simla.com. Для клиентов, которые работают с данным модулем CMS в РФ, ничего не меняется, модули будут также поддерживаться и развиваться.
|
||||
|
||||
Прошу обратить внимание, что в связи с ребрендингом изменилось название ICML-файла названия каталога - simla.xml *(ранее было retailcrm.xml)*.
|
||||
|
||||
Ребрендинг произошел в версии 4.3.0.
|
||||
|
||||
**Работа с более чем одним магазином в Woo**
|
||||
|
||||
В данный момент в модуле не предусмотрена работа с более чем 1 магазином в Woo. <br>
|
||||
|
||||
**Работа с одинаковыми позициями в заказе**
|
||||
|
||||
Модуль поддерживает работу с одинаковыми товарными позициями в заказе начиная с версии 3.5.4 <br>
|
||||
|
||||
**Изменения генерации ICML-файла**
|
||||
В модуле возможно сделать необходимые кастомизация для генерации ICML-файла в желаемом формате <br>
|
||||
|
||||
**Выгрузка архивных данных** <br>
|
||||
|
||||
Ранее модуль мог выгружать не более 700-800 архивных заказов *(т.к. выгрузка происходила по web-хиту, работа скрипта была ограничена и не все данные успевали прогрузится в CRM)*.<br>
|
||||
Сейчас архивные данные можно выгрузить в CRM с использованием консольного скрипта. Этот скрипт позволяет выгружать все архивные заказы и данные о клиентах без ограничения на количество записей. Процесс выгрузки выполняется пакетами по 50 заказов или 50 клиентов за раз. <br>
|
||||
После завершения обработки каждой пачки выводится ее порядковый номер, что позволяет отслеживать прогресс работы скрипта. Если во время выгрузки возникает ошибка, скрипт можно перезапустить с той страницы, где произошел сбой, что минимизирует потерю данных и время на повторную выгрузку.<br>
|
||||
|
||||
Для запуска выгрузки нужно:<br>
|
||||
|
||||
1. В корневой директории вашего сайта (по умолчанию - */var/www/html*) разместить файл **upload_to_crm.php** и вставить в него код:
|
||||
|
||||
```
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/wp-load.php';
|
||||
|
||||
$options = getopt('',['entity::','page::']);
|
||||
|
||||
do_action('wp_console_upload', $options['entity'] ?? '', (int) $options['page'] ?? 0);
|
||||
```
|
||||
2. После чего в командной строке ввести команду для запуска скрипта: <br>
|
||||
|
||||
> php upload_to_crm.php --entity=orders/customers/full_upload --page=номер страницы
|
||||
|
||||
Параметры для выгрузки:<br>
|
||||
|
||||
**--entity**: Указывает, какие данные выгружать. Данный параметр **является обязательным**. Возможные значения:
|
||||
|
||||
- **orders**: архивные заказы;
|
||||
- **customers**: архивные клиенты;
|
||||
- **full_upload**: полная выгрузка всех заказов и клиентов (весь архив).
|
||||
|
||||
**--page**: Указывает номер страницы для выгрузки. Каждая страница содержит 50 заказов или клиентов. Нумерация страниц начинается с 0.<br>
|
||||
|
||||
Пример:
|
||||
|
||||
> php upload_to_crm.php --entity=orders --page=3
|
||||
|
||||
В этом примере будет выгружен архив заказов, начиная с 3-й страницы.
|
||||
|
||||
**Работа с зонами доставки** *(WooCommerce - Настройки - Доставка - Зоны доставки)*
|
||||
|
||||
С зонами доставки модуль не работает
|
||||
|
||||
[https://github.com/retailcrm/woocommerce-module/blob/master/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php#L256](https://github.com/retailcrm/woocommerce-module/blob/master/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php#L256)
|
||||
|
||||
Как только появляются новые методы доставки они автоматически добавляются в настройки CRM. <br>
|
||||
|
||||
**Выгрузка вариативных товаров**
|
||||
|
||||
В модуле добавлен обработка всех типов товаров в WooCommerce. Раньше обрабатывались только variable/simple, теперь, если тип товара не является variation/variable + кастомные типы плагинов содержащие торговые предложения *(variable-subscription и т.п.)*, то обрабатываются как simple. Стандартные типы товаров обрабатываются корректно, но любые кастомные типы с плагинов все равно необходимо исследовать.
|
||||
|
||||
## Причины возникновения ошибок
|
||||
|
||||
**Не пришел заказ из Woo в CRM** <br>
|
||||
Причина может быть в Методе оплаты. Если в настройках CRM *(Настройки - Справочники - Типы оплат)* в конкретном типе оплаты не выбран статус оплаты, с которым пришел заказ из CMS, то заказ не будет выгружен в CRM. Данная проблема должна решиться после реализации задачи [#73631](https://redmine.retailcrm.tech/issues/73631).
|
||||
|
||||

|
||||
|
||||
**Не выгрузились корп клиенты при архивной выгрузке** <br>
|
||||
Необходимо проверить включение данной опции в настройках модуля в WooCommerce.
|
||||
|
||||
**В CRM не отображаются остатки по товару** / **При создании заказа в CRM, товар не списывается со склада в Woocomerce** <br>
|
||||
|
||||
Необходимо выключить редактирование остатков в настройках CRM *(Настройки - Системные - Склад - Разрешить редактирование остатков)*. При включенной опции количество товаров из ICML-файла будет игнорироваться при загрузке. Также для магазина, привязанного к сайту Woo, должны быть разрешены все склады.
|
||||
|
||||
По решению проблемы должны немного помочь задачи [#54708](https://redmine.retailcrm.tech/issues/54708) и [#74803](https://redmine.retailcrm.tech/issues/74803).
|
||||
|
||||
|
||||
**В CRM приходит оплаченный заказ, хотя в Woo оплаты не было** <br>
|
||||
|
||||
Требуется проверить активность опции “Оплата произведена” *(Настройки - Справочники - Статусы оплат)* в статусе оплаты, с которым приходит заказ в CRM.
|
||||
|
||||
**Когда заказ создается через Woocommerce, то ему на почту приходит вся информация по заказу, а если создается через CRM, то на почту приходит чаще всего приходит пустая карточка, но не всегда, от чего это может зависеть?** <br>
|
||||
|
||||
Это не проблема, а логика работы нашего модуля. Модуль не отправляет письма, то есть в Woo сохраняется заказ без суммы (она проставляется чуть позже), но как только заказ появляется в Woo, срабатывает хук и отправляется письмо на email, как раз без суммы.
|
||||
|
||||
**Работоспособность модуля** <br>
|
||||
На работоспособность модуля влияют: <br>
|
||||
**1**) Оплата модуля Онлайн-консультанта. <br>
|
||||
**2**) Для работы модуля требуются файлы generate_icml.php и retailcrm_history.php *(не удалять их)*.
|
||||
|
||||
**Ранее заказы могли не выгружаться из WooCommerce в CRM по причинам** *(сейчас заказы выгружаются, т.к. была убрана валидация)*: <br>
|
||||
|
||||
- **Отсутствовала страна в настройках**. <br> Если в настройках CRM в списке доступных стран (Настройки - Системные - Общие - Список доступных стран) не была выбрана страна, указанная в выгружаемом заказе, то заказ не появлялся в CRM. После внедрения задачи [#74148](https://redmine.retailcrm.tech/issues/74148), заказы с неразрешенной страной приходят в CRM. При открытии карточки заказа, появляется pop-up с информацией о том, что указанную в заказе страну нужно добавить в настройках.
|
||||
|
||||
- **Неразрешенный способ оплаты в доставке**. <br> Если в настройках CRM в справочниках Типов доставок (Настройки - Справочники - Типы доставок - перейти в конкретный справочник - вкладка Способы оплаты) не был разрешен способ оплаты для доставки, указанной в выгружаемом заказе, то заказ не приходил в CRM. После внедрения задачи [#71543](https://redmine.retailcrm.tech/issues/71543), заказы с неразрешенной оплатой для доставки, выгружаются в CRM. При заходе в карточку данного заказа, появляется pop-up с информацией о том, что для доставки требуется разрешить указанный метод оплаты.
|
||||
|
||||
### Не синхронизируется история изменений из Simla.com, не генерируется каталог или не синхронизируются остатки
|
||||
|
||||
Необходимо проверить, выполняет ли wp-cron задачи модуля. В разделе Debug настроек модуля должна быть указана текущая дата (+/- 4 часа).
|
||||
Далее требуется проверить, что все задачи wp-cron выполняются на сайте. Для этого нужно перейти в раздел WooCommerce -> Статус (Estado) -> Статус системы (Estado del sistema) -> WordPress Cron (Cron de WordPress).
|
||||
|
||||
Если wp-cron не выполняет задачи, то в первую очередь нужно восстановить его работу.
|
||||
|
||||
Для быстрого решения можно перезапустить задачи wp-cron в настройках модуля, нажав на кнопку "Очистить" в блоке "Отладочная информация".
|
||||
|
||||
В некоторых случаях задачи модуля можно перенести на cron сервера. Имейте ввиду, что остальные задачи wp-cron останутся в таком же состоянии (не будут выполняться).
|
||||
Действия для переноса:
|
||||
- отключить wp-cron (см. инструкции по WooCommerce и WordPress),
|
||||
- в зависимости от требуемых задач расположить в корневой директории сайта скрипты (в директории с файлом wp-config.php):
|
||||
|
||||
**simla_history.php** - для синхронизации истории
|
||||
```
|
||||
<?php
|
||||
|
||||
/** Load WordPress Bootstrap */
|
||||
require_once dirname( __FILE__ ) . '/wp-load.php';
|
||||
|
||||
do_action("retailcrm_history");
|
||||
```
|
||||
**simla_icml.php** - для генерации каталога
|
||||
```
|
||||
<?php
|
||||
|
||||
/** Load WordPress Bootstrap */
|
||||
require_once dirname( __FILE__ ) . '/wp-load.php';
|
||||
|
||||
do_action("retailcrm_icml");
|
||||
```
|
||||
**simla_stocks.php** - для синхронизации остатков
|
||||
```
|
||||
<?php
|
||||
|
||||
/** Load WordPress Bootstrap */
|
||||
require_once dirname( __FILE__ ) . '/wp-load.php';
|
||||
|
||||
do_action("retailcrm_inventories");
|
||||
```
|
||||
- добавить нужные задачи в расписание cron сервера:
|
||||
```
|
||||
*/5 * * * * {path to php} {wp dir}/simla_history.php
|
||||
* */3 * * * {path to php} {wp dir}/simla_icml.php
|
||||
*/15 * * * * {path to php} {wp dir}/simla_stocks.php
|
||||
```
|
||||
Пример:
|
||||
```
|
||||
*/5 * * * * /usr/local/bin/php /home/conquero/public_html/simla_history.php
|
||||
* */3 * * * /usr/local/bin/php /home/my_site/public_html/simla_icml.php
|
||||
*/15 * * * * /usr/local/bin/php /home/conquero/public_html/simla_stocks.php
|
||||
```
|
1
doc/README.md
Normal file
1
doc/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Developers documentation
|
33
docker-compose.yml
Normal file
33
docker-compose.yml
Normal file
|
@ -0,0 +1,33 @@
|
|||
version: '3'
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ./.docker
|
||||
volumes:
|
||||
- ./:/code
|
||||
links:
|
||||
- "mysql"
|
||||
user: ${UID:-1000}:${GID:-1000}
|
||||
depends_on:
|
||||
- mysql
|
||||
env_file:
|
||||
- ./.env
|
||||
environment:
|
||||
- DB_NAME=${DB_NAME}
|
||||
- DB_USER=${DB_USER}
|
||||
- DB_PASS=${DB_PASS}
|
||||
- DB_HOST=${DB_HOST}
|
||||
- WP_VERSION=${WP_VERSION}
|
||||
- WC_VERSION=${WC_VERSION}
|
||||
- SKIP_DB_CREATE=true
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env_file:
|
||||
- ./.env
|
||||
environment:
|
||||
- MYSQL_DATABASE=${DB_NAME}
|
||||
- MYSQL_USER=${DB_USER}
|
||||
- MYSQL_PASSWORD=${DB_PASS}
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
ports:
|
||||
- "3306:3306"
|
32
phpunit.xml.dist
Normal file
32
phpunit.xml.dist
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
bootstrap="tests/bootstrap.php"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
verbose="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="RetailCRM WooCommerce Test Suite">
|
||||
<directory suffix=".php">tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">src</directory>
|
||||
<exclude>
|
||||
<directory suffix=".php">src/include/api</directory>
|
||||
<directory>src/config</directory>
|
||||
<directory>src/languages</directory>
|
||||
<file>src/readme.txt</file>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<logging>
|
||||
<log type="coverage-clover" target="coverage.xml"/>
|
||||
</logging>
|
||||
</phpunit>
|
611
resources/pot/retailcrm-es_ES.pot
Normal file
611
resources/pot/retailcrm-es_ES.pot
Normal file
|
@ -0,0 +1,611 @@
|
|||
# Translation of Plugins - Woocommerce Simla.com - Development (trunk) in Spanish
|
||||
# This file is distributed under the same license as the Plugins - Woocommerce Simla.com - Development (trunk) package.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2018-06-06 08:53:26+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Generator: GlotPress/2.4.0-alpha\n"
|
||||
"Language: es\n"
|
||||
"Project-Id-Version: Plugins - Woocommerce Simla.com - Development (trunk)\n"
|
||||
|
||||
#. Author URI of the plugin/theme
|
||||
msgid "https://simla.com/"
|
||||
msgstr "https://simla.com/"
|
||||
|
||||
#. Author of the plugin/theme
|
||||
msgid "RetailDriver LLC"
|
||||
msgstr "RetailDriver LLC"
|
||||
|
||||
#. Description of the plugin/theme
|
||||
msgid "Integration plugin for WooCommerce & Simla.com"
|
||||
msgstr "El plugin de integración para WooCommerce & Simla.com"
|
||||
|
||||
#. Plugin URI of the plugin/theme
|
||||
msgid "https://wordpress.org/plugins/woo-retailcrm/"
|
||||
msgstr "https://wordpress.org/plugins/woo-retailcrm/"
|
||||
|
||||
#. Plugin Name of the plugin/theme
|
||||
msgid "WooCommerce Simla.com"
|
||||
msgstr "WooCommerce Simla.com"
|
||||
|
||||
msgid "Orders"
|
||||
msgstr "Pedidos"
|
||||
|
||||
msgid "Customers"
|
||||
msgstr "Clientes"
|
||||
|
||||
msgid "This functionality allows to upload orders to Simla.com differentially"
|
||||
msgstr "Esta función permite la subida selectiva de los pedidos al Simla.com"
|
||||
|
||||
msgid "Uploading orders by identifiers"
|
||||
msgstr "Subida de los pedidos por identificadores"
|
||||
|
||||
msgid "Enter orders identifiers separated by a comma, but no more than 50"
|
||||
msgstr "Introduce los identificadores de pedidos separados por coma, pero no más de 50"
|
||||
|
||||
msgid "Orders identifiers"
|
||||
msgstr "Los identificadores de pedidos"
|
||||
|
||||
msgid "Orders were uploaded"
|
||||
msgstr "Los pedidos están subidos"
|
||||
|
||||
msgid "The field cannot be empty, enter the order ID"
|
||||
msgstr "El campo no puede estar vacío, introduce el identificador de pedido"
|
||||
|
||||
msgid "Catalog was generated"
|
||||
msgstr "El catálogo está generado"
|
||||
|
||||
msgid "Customers and orders were uploaded"
|
||||
msgstr "Los clientes y pedidos están subidos"
|
||||
|
||||
msgid "Enter the correct API key"
|
||||
msgstr "Introduce la llave API correcta"
|
||||
|
||||
msgid "This functionality allows to generate ICML products catalog for uploading to Simla.com"
|
||||
msgstr "Esta función permite generar los catálogos de pedidos ICML para subida al Simla.com"
|
||||
|
||||
msgid "Generating ICML"
|
||||
msgstr "Generando ICML"
|
||||
|
||||
msgid "Generate now"
|
||||
msgstr "Generar ahora"
|
||||
|
||||
msgid "Generating ICML catalog"
|
||||
msgstr "Generar catálogo ICML"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Ajustes"
|
||||
|
||||
msgid "Uploading the existing customers and orders to Simla.com"
|
||||
msgstr "Subida de los clientes existentes y pedidos al Simla.com"
|
||||
|
||||
msgid "Uploading all customers and orders"
|
||||
msgstr "Subir todos los clientes y pedidos"
|
||||
|
||||
msgid "Upload"
|
||||
msgstr "Subir"
|
||||
|
||||
msgid "Settings of uploading"
|
||||
msgstr "Ajustes de subida"
|
||||
|
||||
msgid "User parameter"
|
||||
msgstr "El parámetro personalizado"
|
||||
|
||||
msgid "UA tracking code"
|
||||
msgstr "El código de seguimiento UA"
|
||||
|
||||
msgid "Enable this setting for uploading data to UA"
|
||||
msgstr "Activa esta opción para subir los datos al UA"
|
||||
|
||||
msgid "UA"
|
||||
msgstr "UA"
|
||||
|
||||
msgid "Activate UA"
|
||||
msgstr "Activar UA"
|
||||
|
||||
msgid "UA settings"
|
||||
msgstr "Ajustes de UA"
|
||||
|
||||
msgid "Enable this setting if you would like to get information on leftover stocks from Simla.com to the website"
|
||||
msgstr "Active esta opción si quiere recibir la información del stock de los productos desde Simla.com a la página web"
|
||||
|
||||
msgid "Stock balance"
|
||||
msgstr "El stock"
|
||||
|
||||
msgid "Synchronization of the stock balance"
|
||||
msgstr "Sincronizar el stock"
|
||||
|
||||
msgid "Setting of the stock balance"
|
||||
msgstr "Ajustes del stock"
|
||||
|
||||
msgid "Statuses"
|
||||
msgstr "Los estados"
|
||||
|
||||
msgid "Coupon"
|
||||
msgstr "Cupón"
|
||||
|
||||
msgid "When working with coupons via CRM, it is impossible to transfer manual discounts."
|
||||
msgstr "El trabajo con cupones a través del CRM no permite transferir descuentos manuales."
|
||||
|
||||
msgid "The user field must be in the String or Text format."
|
||||
msgstr "El campo personalizado debe tener el formato Hilo o Texto."
|
||||
|
||||
msgid "When using multiple coupons, separation is supported using spaces, line breaks, characters `;` `,`."
|
||||
msgstr "Si se usan varios cupones, es posible separarlos por espacios, nueva línea o caracteres `;` `,`."
|
||||
|
||||
msgid "For example: code_coupon_1; code_coupon_2, code_coupon_3 code_coupon_4"
|
||||
msgstr "Por ejemplo, code_coupon_1; code_coupon_2, code_coupon_3 code_coupon_4"
|
||||
|
||||
msgid "Payment types"
|
||||
msgstr "Métodos de pago"
|
||||
|
||||
msgid "Delivery types"
|
||||
msgstr "Métodos de envío"
|
||||
|
||||
msgid "Select order methods which will be uploaded from Simla.com to the website"
|
||||
msgstr "Elige el método de formalización de los pedidos que se van a subir desde Simla.com a la página web"
|
||||
|
||||
msgid "Order methods available for uploading from Simla.com"
|
||||
msgstr "Los métodos de la formalización de los pedidos disponibles para subida desde Simla.com"
|
||||
|
||||
msgid "Order methods"
|
||||
msgstr "Los métodos de la formalización del pedido"
|
||||
|
||||
msgid "Catalog settings"
|
||||
msgstr "Ajustes del catálogo"
|
||||
|
||||
msgid "Online assistant"
|
||||
msgstr "Consultor en línea"
|
||||
|
||||
msgid "Insert the Online consultant code here"
|
||||
msgstr "Inserte el código de consultor en Línea aquí"
|
||||
|
||||
msgid "Select API version"
|
||||
msgstr "Selecciona la versión de API"
|
||||
|
||||
msgid "API version"
|
||||
msgstr "Versión de API"
|
||||
|
||||
msgid "API settings"
|
||||
msgstr "Ajustes de API"
|
||||
|
||||
msgid "Enter your API key. You can find it in the administration section of Simla.com"
|
||||
msgstr "Introduce la llave API. Puede encontrarla en apartado administrativo del Simla.com"
|
||||
|
||||
msgid "Enter API URL (https://yourdomain.simla.com)"
|
||||
msgstr "Introduce enlace de API (https://yourdomain.simla.com)"
|
||||
|
||||
msgid "Integration with Simla.com management system"
|
||||
msgstr "La integración con el sistema de gestión del Simla.com"
|
||||
|
||||
msgid "Every 15 minutes"
|
||||
msgstr "Cada 15 minutos"
|
||||
|
||||
msgid "Every 3 hours"
|
||||
msgstr "Cada 3 horas"
|
||||
|
||||
msgid "Every 5 minutes"
|
||||
msgstr "Cada 5 minutos"
|
||||
|
||||
msgid "API Key"
|
||||
msgstr "La llave API"
|
||||
|
||||
msgid "API of URL"
|
||||
msgstr "El enlace API"
|
||||
|
||||
msgid "Main settings"
|
||||
msgstr "Los ajustes generales"
|
||||
|
||||
msgid "Simla.com"
|
||||
msgstr "Simla.com"
|
||||
|
||||
msgid "Daemon Collector settings"
|
||||
msgstr "Ajustes de Daemon Collector"
|
||||
|
||||
msgid "Activate Daemon Collector"
|
||||
msgstr "Activar Daemon Collector"
|
||||
|
||||
msgid "Daemon Collector"
|
||||
msgstr "Daemon Collector"
|
||||
|
||||
msgid "Enable this setting for activate Daemon Collector on site"
|
||||
msgstr "Active esta configuración para activar Daemon Collector en la página web"
|
||||
|
||||
msgid "Site key"
|
||||
msgstr "Clave de la página web"
|
||||
|
||||
msgid "Disable data editing in Simla.com"
|
||||
msgstr "Desactivar edición de datos en Simla.com"
|
||||
|
||||
msgid "Data updating in Simla.com"
|
||||
msgstr "Actualización de datos en Simla.com"
|
||||
|
||||
msgid "Activate the binding via sku (xml)"
|
||||
msgstr "Activar conexión por sku (xmlId)"
|
||||
|
||||
msgid "Stock synchronization and link between products"
|
||||
msgstr "Sincronización de stock y conexión de productos"
|
||||
|
||||
msgid "Enable transferring the number to Simla.com"
|
||||
msgstr "Activar la transferencia de números en Simla.com"
|
||||
|
||||
msgid "Transferring the order number"
|
||||
msgstr "Transferencia de un número de pedido"
|
||||
|
||||
msgid "Corporate customers support"
|
||||
msgstr "Soporte a clientes corporativos"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "Habilitado"
|
||||
|
||||
msgid "Settings of WhatsApp"
|
||||
msgstr "Ajustes de WhatsApp"
|
||||
|
||||
msgid "Activar WhatsApp"
|
||||
msgstr "Активировать WhatsApp"
|
||||
|
||||
msgid "Activate this setting to activate WhatsApp on the website"
|
||||
msgstr "Activa esta configuración para activar WhatsApp en la página web"
|
||||
|
||||
msgid "WhatsApp icon location"
|
||||
msgstr "Ubicación del ícono de WhatsApp"
|
||||
|
||||
msgid "Place in the lower right corner of the website"
|
||||
msgstr "Colocar en la esquina inferior derecha de la pagina web"
|
||||
|
||||
msgid "By default, WhatsApp icon is located in the lower left corner of the website"
|
||||
msgstr "Por defecto, el ícono de WhatsApp se encuentra en la esquina inferior izquierda de la página web"
|
||||
|
||||
msgid "Enter your phone number"
|
||||
msgstr "Introduce tu número de teléfono"
|
||||
|
||||
msgid "WhatsApp chat will be opened with this contact"
|
||||
msgstr "Se abrirá una ventana de chat con este contacto en WhatsApp"
|
||||
|
||||
msgid "Introduce the correct phone number"
|
||||
msgstr "Introduce el número de teléfono correcto"
|
||||
|
||||
msgid "You can export all orders and customers from CMS to Simla.com by clicking the «Upload» button. This process can take much time and before it is completed, you need to keep the tab open"
|
||||
msgstr "Presionando el botón «Exportar» puedes descargar a todos los pedidos y clientes de CMS a Simla.com. Este proceso puede llevar mucho tiempo y es necesario mantener abierta la pestaña hasta que termine el proceso"
|
||||
|
||||
msgid "Debug information"
|
||||
msgstr "Información Debug"
|
||||
|
||||
msgid "Custom fields"
|
||||
msgstr "Campos personalizados"
|
||||
|
||||
msgid "Select value"
|
||||
msgstr "Selecciona un valor"
|
||||
|
||||
msgid "Custom fields for order"
|
||||
msgstr "Campos de pedido personalizados"
|
||||
|
||||
msgid "Custom fields for customer"
|
||||
msgstr "Campos de clientes personalizados"
|
||||
|
||||
msgid "Add new select for order"
|
||||
msgstr "Añadir"
|
||||
|
||||
msgid "Add new select for customer"
|
||||
msgstr "Añadir"
|
||||
|
||||
msgid "This option is disabled"
|
||||
msgstr "Opción desactivada"
|
||||
|
||||
msgid "Cron launches"
|
||||
msgstr "Tareas de cron"
|
||||
|
||||
msgid "Generation ICML"
|
||||
msgstr "Generación del catálogo"
|
||||
|
||||
msgid "Syncing history"
|
||||
msgstr "Sincronización del historial"
|
||||
|
||||
msgid "Syncing inventories"
|
||||
msgstr "Sincronización de inventario"
|
||||
|
||||
msgid "Don't send to CRM"
|
||||
msgstr "No enviar al CRM"
|
||||
|
||||
msgid "Integration payment"
|
||||
msgstr "Integración pago"
|
||||
|
||||
msgid "Attention!"
|
||||
msgstr "¡Atención!"
|
||||
|
||||
msgid "If payment type linked to the CRM integration module choosed, payment must be proceed in the CRM"
|
||||
msgstr "Al seleccionar en el enlace de tipos de pago un método de pago de integración en CRM, el pago se debe realizar por parte del CRM"
|
||||
|
||||
msgid "Product description"
|
||||
msgstr "Descripción del Producto"
|
||||
|
||||
msgid "Full description"
|
||||
msgstr "Descripción completa"
|
||||
|
||||
msgid "Short description"
|
||||
msgstr "Descripción corta"
|
||||
|
||||
msgid "In the catalog, you can use a full or short description of the product"
|
||||
msgstr "En el catálogo, puedes utilizar una descripción del producto corta o completa"
|
||||
|
||||
msgid "If you change the time interval, need to clear the old cron tasks"
|
||||
msgstr "Si cambias el Intervalo de Tiempo tienes que limpiar los cron tareas antiguos"
|
||||
|
||||
msgid "Clear cron tasks"
|
||||
msgstr "Borrar tareas cron"
|
||||
|
||||
msgid "Clear"
|
||||
msgstr "Borrar"
|
||||
|
||||
msgid "Cron tasks cleared"
|
||||
msgstr "Trabajos cron borrados"
|
||||
|
||||
msgid "Untitled"
|
||||
msgstr "Intitulado"
|
||||
|
||||
msgid "Incorrect protocol. Only https is allowed."
|
||||
msgstr "Protocolo incorrecto. Sólo se permite https."
|
||||
|
||||
msgid "The domain path must be empty."
|
||||
msgstr "La ruta del dominio debe estar vacía."
|
||||
|
||||
msgid "An invalid domain is specified."
|
||||
msgstr "Se especifica un dominio no válido."
|
||||
|
||||
msgid "The port does not need to be specified."
|
||||
msgstr "No es necesario especificar el puerto."
|
||||
|
||||
msgid "Incorrect Host URL."
|
||||
msgstr "URL del Host incorrecta."
|
||||
|
||||
msgid "Incorrect URL."
|
||||
msgstr "URL incorrecta."
|
||||
|
||||
msgid "The query must be blank."
|
||||
msgstr "La consulta debe estar en blanco."
|
||||
|
||||
msgid "The fragment should be blank."
|
||||
msgstr "El fragmento debe estar en blanco."
|
||||
|
||||
msgid "No need to provide authorization data."
|
||||
msgstr "No es necesario proporcionar datos de autorización."
|
||||
|
||||
msgid "Unable to obtain reference values."
|
||||
msgstr "No se pueden obtener valores de referencia."
|
||||
|
||||
msgid "Abandoned carts"
|
||||
msgstr "Carritos Abandonadas"
|
||||
|
||||
msgid "Upload abandoned carts"
|
||||
msgstr "Importar los carritos abandonados"
|
||||
|
||||
msgid "Enable if you want to in CRM abandoned shopping carts were unloaded"
|
||||
msgstr "Habilitar Si desea que en CRM se descargaron las cestas abandonadas de los compradores"
|
||||
|
||||
msgid "firstName"
|
||||
msgstr "Nombre"
|
||||
|
||||
msgid "lastName"
|
||||
msgstr "Apellido"
|
||||
|
||||
msgid "phone"
|
||||
msgstr "Número de teléfono"
|
||||
|
||||
msgid "tags"
|
||||
msgstr "Etiquetas"
|
||||
|
||||
msgid "customerComment"
|
||||
msgstr "Comentario del cliente"
|
||||
|
||||
msgid "managerComment"
|
||||
msgstr "Comentario del asesor"
|
||||
|
||||
msgid "email"
|
||||
msgstr "E-mail"
|
||||
|
||||
msgid "addressText"
|
||||
msgstr "Dirección"
|
||||
|
||||
msgid "addressCity"
|
||||
msgstr "Ciudad"
|
||||
|
||||
msgid "addressIndex"
|
||||
msgstr "Código postal"
|
||||
|
||||
msgid "addressRegion"
|
||||
msgstr "Región"
|
||||
|
||||
msgid "Standard CRM fields"
|
||||
msgstr "Los campos del CRM por defecto"
|
||||
|
||||
msgid "Warehouses available in CRM"
|
||||
msgstr "Almacenes disponibles en CRM"
|
||||
|
||||
msgid "Select warehouses to receive balances from CRM. To select several warehouses, hold down CTRL (for Windows and Linux) or ⌘ Command (for MacOS)"
|
||||
msgstr "Selecciona los almacenes para recibir el stock desde CRM. Para seleccionar varios mantén pulsado CTRL (para Windows y Linux) o ⌘ Command (para MacOS)"
|
||||
|
||||
msgid "I agree to receive promotional newsletters"
|
||||
msgstr "Estoy de acuerdo en recibir los boletines informativos"
|
||||
|
||||
msgid "API key with one-shop access required"
|
||||
msgstr "Se requiere clave API con acceso a una tienda"
|
||||
|
||||
msgid "The currency of the site differs from the currency of the store in CRM. For the integration to work correctly, the currencies in CRM and CMS must match"
|
||||
msgstr "La moneda del sitio web es distinto a la tienda del CRM. Para el funcionamiento correcto de la integración, las monedas del CMS y CRM deben coincid"
|
||||
|
||||
msgid "Loyalty program"
|
||||
msgstr "Programa de fidelización"
|
||||
|
||||
msgid "Activate program loyalty"
|
||||
msgstr "Activar programa de fidelización"
|
||||
|
||||
msgid "Attention! When activating the loyalty program, the method of ICML catalog generation changes. Details in"
|
||||
msgstr "¡Atención! Al activar el programa de fidelización, cambia el método de generación del catálogo ICML. Detalles en la"
|
||||
|
||||
msgid "<a href='https://docs.simla.com/Users/Integration/SiteModules/WooCommerce/PLWoocommerce'>documentation loyalty program</a>"
|
||||
msgstr "<a href='https://docs.simla.com/es/Users/Integration/SiteModules/WooCommerce/PLWoocommerce'>documentación del programa de fidelización</a>"
|
||||
|
||||
msgid "Terms of loyalty program"
|
||||
msgstr "Condiciones del programa de fidelización"
|
||||
|
||||
msgid "Insert the terms and conditions of the loyalty program"
|
||||
msgstr "Introduce las condiciones del programa de fidelización"
|
||||
|
||||
msgid "Conditions of personal data processing"
|
||||
msgstr "Condiciones de procesamiento de datos personales"
|
||||
|
||||
msgid "Insert the terms and conditions for processing personal data"
|
||||
msgstr "Introduce las condiciones para el procesamiento de datos personales"
|
||||
|
||||
msgid "To activate the loyalty program it is necessary to activate the <a href='?page=wc-settings'>'enable use of coupons option'</a>"
|
||||
msgstr "Para activar el programa de fidelización es necesario activar la opción <a href='?page=wc-settings'>'habilitar el uso de cupones'</a>"
|
||||
|
||||
msgid "Bonus account"
|
||||
msgstr "Cuenta de bonos"
|
||||
|
||||
msgid "Participation ID: "
|
||||
msgstr "ID de participación: "
|
||||
|
||||
msgid "Current level: "
|
||||
msgstr "Nivel actual: "
|
||||
|
||||
msgid "Bonuses on the account: "
|
||||
msgstr "Bonos en la cuenta: "
|
||||
|
||||
msgid "Bonus card number: "
|
||||
msgstr "Número de tarjeta de bonos: "
|
||||
|
||||
msgid "Date of registration: "
|
||||
msgstr "Fecha de registro: "
|
||||
|
||||
msgid "Current level rules"
|
||||
msgstr "Reglas del nivel actual"
|
||||
|
||||
msgid "Required amount of purchases to move to the next level: "
|
||||
msgstr "Cantidad de compras necesarias para pasar al siguiente nivel: "
|
||||
|
||||
msgid "Activate participation in the loyalty program"
|
||||
msgstr "Activar participación en el programa de fidelización"
|
||||
|
||||
msgid "Send"
|
||||
msgstr "Enviar"
|
||||
|
||||
msgid "To register in the loyalty program, fill in the form:"
|
||||
msgstr "Para registrarse en el programa de fidelización, rellena el formulario:"
|
||||
|
||||
msgid " I agree with "
|
||||
msgstr " Estoy de acuerdo con "
|
||||
|
||||
msgid "loyalty program terms"
|
||||
msgstr "términos del programa de fidelización"
|
||||
|
||||
msgid "terms of personal data processing"
|
||||
msgstr "términos de procesamiento de datos personales"
|
||||
|
||||
msgid "Phone"
|
||||
msgstr "Teléfono"
|
||||
|
||||
msgid "Error while registering in the loyalty program. Try again later"
|
||||
msgstr "Error al registrarse en el programa de fidelización. Inténtalo de nuevo más tarde"
|
||||
|
||||
msgid "The card is not linked"
|
||||
msgstr "La tarjeta no está vinculada"
|
||||
|
||||
msgid "Error while retrieving data. Try again later"
|
||||
msgstr "Error al recuperar los datos. Inténtalo de nuevo más tarde"
|
||||
|
||||
msgid "Error when activating the loyalty program. Try again later"
|
||||
msgstr "Error al activar el programa de fidelización. Inténtalo de nuevo más tarde"
|
||||
|
||||
msgid "Enter the correct phone number"
|
||||
msgstr "Introduce el número de teléfono correcto"
|
||||
|
||||
msgid "Close"
|
||||
msgstr "Cerrar"
|
||||
|
||||
msgid "Ordinary products: accrual of 1 bonus for each %s %s"
|
||||
msgstr "Productos ordinarios: acumulación de 1 bono por cada %s %s"
|
||||
|
||||
msgid "Promotional products: accrual of 1 bonus for each %s %s"
|
||||
msgstr "Productos promocionales: acumulación de 1 bono por cada %s %s"
|
||||
|
||||
msgid "Ordinary products: bonus accrual in the amount of %s%% of the purchase amount"
|
||||
msgstr "Productos ordinarios: acumulación de bonos en la cantidad de %s%% de la suma de la compra"
|
||||
|
||||
msgid "Promotional products: bonus accrual in the amount of %s%% of the purchase amount"
|
||||
msgstr "Productos promocionales: acumulación de bonos en la cantidad de %s%% de la suma de la compra"
|
||||
|
||||
msgid "Ordinary products: %s%% discount"
|
||||
msgstr "Productos ordinarios: %s%% de descuento"
|
||||
|
||||
msgid "Promotional products: %s%% discount"
|
||||
msgstr "Productos promocionales: %s%% de descuento"
|
||||
|
||||
msgid "Uploading services"
|
||||
msgstr "Subida de servicios"
|
||||
|
||||
msgid "Goods with the 'virtual' option enabled will be uploaded to Simla as services"
|
||||
msgstr "Los productos con la opción 'virtual' activada se subirán a Simla como servicios"
|
||||
|
||||
msgid "User not found in the system"
|
||||
msgstr "Usuario no encontrado en el sistema"
|
||||
|
||||
msgid "Error when searching for participation in loyalty programs"
|
||||
msgstr "Error al buscar la participación en programas de fidelización"
|
||||
|
||||
msgid "No active participation in the loyalty program was detected"
|
||||
msgstr "No se detectó la participación activa en el programa de fidelización"
|
||||
|
||||
msgid "No bonuses for debiting"
|
||||
msgstr "No hay bonos para debitar"
|
||||
|
||||
msgid "Loyalty program not found"
|
||||
msgstr "Programa de fidelización no encontrado"
|
||||
|
||||
msgid "Loyalty program is not active"
|
||||
msgstr "Programa de fidelización no está activo"
|
||||
|
||||
msgid "Loyalty program blocked"
|
||||
msgstr "Programa de fidelización bloqueado"
|
||||
|
||||
msgid "This user is a corporate person"
|
||||
msgstr "Este usuario es una persona jurídica"
|
||||
|
||||
msgid "It is possible to write off"
|
||||
msgstr "Es posible debitar"
|
||||
|
||||
msgid "bonuses"
|
||||
msgstr "bonos"
|
||||
|
||||
msgid "Use coupon:"
|
||||
msgstr "Utiliza el cupón:"
|
||||
|
||||
msgid "Points will be awarded upon completion of the order:"
|
||||
msgstr "Los puntos se concederán al finalizar el pedido:"
|
||||
|
||||
msgid "Unloading promotional prices of offers"
|
||||
msgstr "Descarga de precios promocionales de ofertas comerciales"
|
||||
|
||||
msgid "Every 4 hours"
|
||||
msgstr "Cada 4 horas"
|
||||
|
||||
msgid "Upload prices now"
|
||||
msgstr "Descargar precios ahora"
|
||||
|
||||
msgid "Uploaded discount price"
|
||||
msgstr "Descarga de precios promocionales"
|
||||
|
||||
msgid "This functionality loads the promotional prices offers into Simla.com"
|
||||
msgstr "Esta función carga los precios promocionales de las ofertas comerciales en Simla.com"
|
||||
|
||||
msgid "Promotional prices unloaded"
|
||||
msgstr "Se han cargado los precios promocionales"
|
||||
|
||||
msgid "Woocommerce promotional price"
|
||||
msgstr "Precio promocional Woocommerce"
|
||||
|
||||
msgid "Promotional price type for Woocommerce store, generated automatically. Necessary for correct synchronization work when loyalty program is enabled (Do not delete. Do not deactivate)"
|
||||
msgstr "Tipo de precio promocional para la tienda Woocommerce, generado automáticamente. Necesario para el correcto funcionamiento de la sincronización cuando el programa de fidelización está habilitado (No eliminar. No desactivar)"
|
620
resources/pot/retailcrm-ru_RU.pot
Normal file
620
resources/pot/retailcrm-ru_RU.pot
Normal file
|
@ -0,0 +1,620 @@
|
|||
# Translation of Plugins - Woocommerce Simla.com - Development (trunk) in Russian
|
||||
# This file is distributed under the same license as the Plugins - Woocommerce Simla.com - Development (trunk) package.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2018-06-06 08:53:26+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Generator: GlotPress/2.4.0-alpha\n"
|
||||
"Language: ru\n"
|
||||
"Project-Id-Version: Plugins - Woocommerce Simla.com - Development (trunk)\n"
|
||||
|
||||
#. Author URI of the plugin/theme
|
||||
msgid "https://simla.com/"
|
||||
msgstr "https://simla.com/"
|
||||
|
||||
#. Author of the plugin/theme
|
||||
msgid "RetailDriver LLC"
|
||||
msgstr "RetailDriver LLC"
|
||||
|
||||
#. Description of the plugin/theme
|
||||
msgid "Integration plugin for WooCommerce & Simla.com"
|
||||
msgstr "Интеграционный плагин для WooCommerce & Simla.com"
|
||||
|
||||
#. Plugin URI of the plugin/theme
|
||||
msgid "https://wordpress.org/plugins/woo-retailcrm/"
|
||||
msgstr "https://wordpress.org/plugins/woo-retailcrm/"
|
||||
|
||||
#. Plugin Name of the plugin/theme
|
||||
msgid "WooCommerce Simla.com"
|
||||
msgstr "WooCommerce Simla.com"
|
||||
|
||||
msgid "Orders"
|
||||
msgstr "Заказы"
|
||||
|
||||
msgid "Customers"
|
||||
msgstr "Клиенты"
|
||||
|
||||
msgid "Activate history uploads"
|
||||
msgstr "Активировать загрузку истории изменений"
|
||||
|
||||
msgid "Upload data from Simla.com"
|
||||
msgstr "Загрузка данных из Simla.com"
|
||||
|
||||
msgid "Generating ICML catalog by wp-cron"
|
||||
msgstr "Генерация ICML каталога товаров с помощью wp-cron"
|
||||
|
||||
msgid "This functionality allows to upload orders to Simla.com differentially"
|
||||
msgstr "Эта функция позволяет производить выборочную выгрузку заказов в Simla.com"
|
||||
|
||||
msgid "Uploading orders by identifiers"
|
||||
msgstr "Выгрузка заказов по идентификаторам"
|
||||
|
||||
msgid "Enter orders identifiers separated by a comma, but no more than 50"
|
||||
msgstr "Введите идентификаторы заказов через запятую, но не более 50"
|
||||
|
||||
msgid "Orders identifiers"
|
||||
msgstr "Идентификаторы заказов"
|
||||
|
||||
msgid "Orders were uploaded"
|
||||
msgstr "Заказы были выгружены"
|
||||
|
||||
msgid "The field cannot be empty, enter the order ID"
|
||||
msgstr "Поле не может быть пустым, введите идентификатор заказа"
|
||||
|
||||
msgid "Catalog was generated"
|
||||
msgstr "Каталог был сгенерирован"
|
||||
|
||||
msgid "Customers and orders were uploaded"
|
||||
msgstr "Клиенты и заказы были выгружены"
|
||||
|
||||
msgid "Enter the correct API key"
|
||||
msgstr "Введите корректный API ключ"
|
||||
|
||||
msgid "This functionality allows to generate ICML products catalog for uploading to Simla.com"
|
||||
msgstr "Эта функция позволяет сгенерировать ICML каталог товаров для выгрузки в Simla.com"
|
||||
|
||||
msgid "Generating ICML"
|
||||
msgstr "Генерация ICML"
|
||||
|
||||
msgid "Generate now"
|
||||
msgstr "Сгенерировать сейчас"
|
||||
|
||||
msgid "Generating ICML catalog"
|
||||
msgstr "Генерация ICML каталога"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "Uploading the existing customers and orders to Simla.com"
|
||||
msgstr "Выгрузка существующих клиентов и заказов в Simla.com"
|
||||
|
||||
msgid "Uploading all customers and orders"
|
||||
msgstr "Выгрузка всех клиентов и заказов"
|
||||
|
||||
msgid "Upload"
|
||||
msgstr "Выгрузить"
|
||||
|
||||
msgid "Settings of uploading"
|
||||
msgstr "Настройки выгрузки"
|
||||
|
||||
msgid "User parameter"
|
||||
msgstr "Пользовательский параметр"
|
||||
|
||||
msgid "UA tracking code"
|
||||
msgstr "Код отслеживания UA"
|
||||
|
||||
msgid "Enable this setting for uploading data to UA"
|
||||
msgstr "Активируйте эту настройку для выгрузки данных в UA"
|
||||
|
||||
msgid "UA"
|
||||
msgstr "UA"
|
||||
|
||||
msgid "Activate UA"
|
||||
msgstr "Активировать UA"
|
||||
|
||||
msgid "UA settings"
|
||||
msgstr "Настройки UA"
|
||||
|
||||
msgid "Enable this setting if you would like to get information on leftover stocks from Simla.com to the website"
|
||||
msgstr "Активируйте данную настройку, если хотите получать остатки по товарам из Simla.com на сайт"
|
||||
|
||||
msgid "Stock balance"
|
||||
msgstr "Остатки"
|
||||
|
||||
msgid "Synchronization of the stock balance"
|
||||
msgstr "Синхронизация остатков"
|
||||
|
||||
msgid "Setting of the stock balance"
|
||||
msgstr "Настройка управления остатками"
|
||||
|
||||
msgid "Statuses"
|
||||
msgstr "Статусы"
|
||||
|
||||
msgid "Coupon"
|
||||
msgstr "Купон"
|
||||
|
||||
msgid "When working with coupons via CRM, it is impossible to transfer manual discounts."
|
||||
msgstr "При работе с купонами через CRM невозможно передавать ручные скидки."
|
||||
|
||||
msgid "The user field must be in the String or Text format."
|
||||
msgstr "Пользовательское поле должно быть формата Строка или Текст."
|
||||
|
||||
msgid "When using multiple coupons, separation is supported using spaces, line breaks, characters `;` `,`."
|
||||
msgstr "При использовании нескольких купонов, поддерживается разделение с помощью пробелов, переноса строки, символами `;` `,`."
|
||||
|
||||
msgid "For example: code_coupon_1; code_coupon_2, code_coupon_3 code_coupon_4"
|
||||
msgstr "Например: code_coupon_1; code_coupon_2, code_coupon_3 code_coupon_4"
|
||||
|
||||
msgid "Payment types"
|
||||
msgstr "Способы оплаты"
|
||||
|
||||
msgid "Delivery types"
|
||||
msgstr "Способы доставки"
|
||||
|
||||
msgid "Select order methods which will be uploaded from Simla.com to the website"
|
||||
msgstr "Выберите способы оформления заказов, которые будут выгружаться из Simla.com на сайт"
|
||||
|
||||
msgid "Order methods available for uploading from Simla.com"
|
||||
msgstr "Способы оформления заказа, доступные для выгрузки из Simla.com"
|
||||
|
||||
msgid "Order methods"
|
||||
msgstr "Способы оформления заказа"
|
||||
|
||||
msgid "Catalog settings"
|
||||
msgstr "Настройки каталога"
|
||||
|
||||
msgid "Online assistant"
|
||||
msgstr "Онлайн консультант"
|
||||
|
||||
msgid "Insert the Online consultant code here"
|
||||
msgstr "Вставьте код Онлайн-консультанта здесь"
|
||||
|
||||
msgid "Select API version"
|
||||
msgstr "Выберите версию API"
|
||||
|
||||
msgid "API version"
|
||||
msgstr "Версия API"
|
||||
|
||||
msgid "API settings"
|
||||
msgstr "Настройки API"
|
||||
|
||||
msgid "Enter your API key. You can find it in the administration section of Simla.com"
|
||||
msgstr "Введите API ключ. Вы можете найти его в административном разделе Simla.com"
|
||||
|
||||
msgid "Enter API URL (https://yourdomain.simla.com)"
|
||||
msgstr "Введите API URL (https://yourdomain.simla.com)"
|
||||
|
||||
msgid "Integration with Simla.com management system"
|
||||
msgstr "Интеграция с системой управления Simla.com"
|
||||
|
||||
msgid "Every 15 minutes"
|
||||
msgstr "Каждые 15 минут"
|
||||
|
||||
msgid "Every 3 hours"
|
||||
msgstr "Каждые 3 часа"
|
||||
|
||||
msgid "Every 5 minutes"
|
||||
msgstr "Каждые 5 минут"
|
||||
|
||||
msgid "API key"
|
||||
msgstr "API ключ"
|
||||
|
||||
msgid "API of URL"
|
||||
msgstr "API URL"
|
||||
|
||||
msgid "Main settings"
|
||||
msgstr "Главные настройки"
|
||||
|
||||
msgid "Simla.com"
|
||||
msgstr "Simla.com"
|
||||
|
||||
msgid "Daemon Collector settings"
|
||||
msgstr "Настройка Daemon Collector"
|
||||
|
||||
msgid "Activate Daemon Collector"
|
||||
msgstr "Активировать Daemon Collector"
|
||||
|
||||
msgid "Daemon Collector"
|
||||
msgstr "Daemon Collector"
|
||||
|
||||
msgid "Enable this setting for activate Daemon Collector on site"
|
||||
msgstr "Активируйте эту настройку для активации Daemon Collector на сайте"
|
||||
|
||||
msgid "Site key"
|
||||
msgstr "Ключ сайта"
|
||||
|
||||
msgid "Disable data editing in Simla.com"
|
||||
msgstr "Деактивировать редактирование данных в Simla.com"
|
||||
|
||||
msgid "Data updating in Simla.com"
|
||||
msgstr "Обновление данных в Simla.com"
|
||||
|
||||
msgid "Activate the binding via sku (xml)"
|
||||
msgstr "Активировать связь по sku(xmlId)"
|
||||
|
||||
msgid "Stock synchronization and link between products"
|
||||
msgstr "Синхронизация остатков и связь товаров"
|
||||
|
||||
msgid "Enable transferring the number to Simla.com"
|
||||
msgstr "Активировать передачу номера в Simla.com"
|
||||
|
||||
msgid "Transferring the order number"
|
||||
msgstr "Передача номера заказа"
|
||||
|
||||
msgid "Corporate customers support"
|
||||
msgstr "Поддержка корпоративных клиентов"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "Включено"
|
||||
|
||||
msgid "Settings of WhatsApp"
|
||||
msgstr "Настройки WhatsApp"
|
||||
|
||||
msgid "Activate WhatsApp"
|
||||
msgstr "Активировать WhatsApp"
|
||||
|
||||
msgid "Activate this setting to activate WhatsApp on the website"
|
||||
msgstr "Активируйте эту настройку для активации WhatsApp на сайте"
|
||||
|
||||
msgid "WhatsApp icon location"
|
||||
msgstr "Расположение иконки WhatsApp"
|
||||
|
||||
msgid "Place in the lower right corner of the website"
|
||||
msgstr "Разместить в правом нижнем углу сайта"
|
||||
|
||||
msgid "By default, WhatsApp icon is located in the lower left corner of the website"
|
||||
msgstr "По умолчанию иконка WhatsApp расположена в левом нижнем углу сайта"
|
||||
|
||||
msgid "Enter your phone number"
|
||||
msgstr "Введите номер телефона"
|
||||
|
||||
msgid "WhatsApp chat will be opened with this contact"
|
||||
msgstr "Будет открыт чат в WhatsApp с данным контактом"
|
||||
|
||||
msgid "Introduce the correct phone number"
|
||||
msgstr "Введите корректный номер телефона"
|
||||
|
||||
msgid "You can export all orders and customers from CMS to Simla.com by clicking the «Upload» button. This process can take much time and before it is completed, you need to keep the tab open"
|
||||
msgstr "Вы можете экспортировать все заказы и клиентов из CMS в Simla.com, нажав кнопку «Выгрузить». Этот процесс может занять много времени, и до его завершения необходимо держать вкладку открытой"
|
||||
|
||||
msgid "Debug information"
|
||||
msgstr "Отладочная информация"
|
||||
|
||||
msgid "Custom fields"
|
||||
msgstr "Пользовательские поля"
|
||||
|
||||
msgid "Select value"
|
||||
msgstr "Выберите значение"
|
||||
|
||||
msgid "Custom fields for order"
|
||||
msgstr "Пользовательские поля для заказа"
|
||||
|
||||
msgid "Custom fields for customer"
|
||||
msgstr "Пользовательские поля для клиента"
|
||||
|
||||
msgid "Add new select for order"
|
||||
msgstr "Добавить"
|
||||
|
||||
msgid "Add new select for customer"
|
||||
msgstr "Добавить"
|
||||
|
||||
msgid "This option is disabled"
|
||||
msgstr "Опция отключена"
|
||||
|
||||
msgid "Cron launches"
|
||||
msgstr "Работающие крон задачи"
|
||||
|
||||
msgid "Generation ICML"
|
||||
msgstr "Генерация каталога"
|
||||
|
||||
msgid "Syncing history"
|
||||
msgstr "Синхронизация истории"
|
||||
|
||||
msgid "Syncing inventories"
|
||||
msgstr "Синхронизация запасов"
|
||||
|
||||
msgid "Don't send to CRM"
|
||||
msgstr "Не отправлять в CRM"
|
||||
|
||||
msgid "Integration payment"
|
||||
msgstr "Интеграционная оплата"
|
||||
|
||||
msgid "Attention!"
|
||||
msgstr "Внимание!"
|
||||
|
||||
msgid "If payment type linked to the CRM integration module choosed, payment must be proceed in the CRM"
|
||||
msgstr "При указании в соответствии типа оплаты, привязанного к интеграционному модулю в CRM, оплата должна происходить на стороне CRM"
|
||||
|
||||
msgid "Product description"
|
||||
msgstr "Описание товара"
|
||||
|
||||
msgid "Full description"
|
||||
msgstr "Полное описание"
|
||||
|
||||
msgid "Short description"
|
||||
msgstr "Краткое описание"
|
||||
|
||||
msgid "In the catalog, you can use a full or short description of the product"
|
||||
msgstr "В каталоге можно использовать полное или краткое описание товара"
|
||||
|
||||
msgid "If you change the time interval, need to clear the old cron tasks"
|
||||
msgstr "Если вы изменили временной интервал, необходимо очистить старые cron задачи"
|
||||
|
||||
msgid "Clear cron tasks"
|
||||
msgstr "Очистить cron задачи"
|
||||
|
||||
msgid "Clear"
|
||||
msgstr "Очистить"
|
||||
|
||||
msgid "Cron tasks cleared"
|
||||
msgstr "Cron задачи очищены"
|
||||
|
||||
msgid "Untitled"
|
||||
msgstr "Без названия"
|
||||
|
||||
msgid "Incorrect protocol. Only https is allowed."
|
||||
msgstr "Некорректный протокол. Допускается только https."
|
||||
|
||||
msgid "The domain path must be empty."
|
||||
msgstr "Путь к домену должен быть пустым."
|
||||
|
||||
msgid "An invalid domain is specified."
|
||||
msgstr "Указан недопустимый домен."
|
||||
|
||||
msgid "The port does not need to be specified."
|
||||
msgstr "Не нужно указывать порт."
|
||||
|
||||
msgid "Incorrect Host URL."
|
||||
msgstr "Некорректный URL хоста"
|
||||
|
||||
msgid "Incorrect URL."
|
||||
msgstr "Некорректный URL."
|
||||
|
||||
msgid "The query must be blank."
|
||||
msgstr "Запрос должен быть пустым."
|
||||
|
||||
msgid "The fragment should be blank."
|
||||
msgstr "Фрагмент должен быть пустым."
|
||||
|
||||
msgid "No need to provide authorization data."
|
||||
msgstr "Нет необходимости предоставлять авторизационные данные."
|
||||
|
||||
msgid "Unable to obtain reference values."
|
||||
msgstr "Не удалось получить эталонное значение"
|
||||
|
||||
msgid "Abandoned carts"
|
||||
msgstr "Брошенные корзины"
|
||||
|
||||
msgid "Upload abandoned carts"
|
||||
msgstr "Выгружать брошенные корзины"
|
||||
|
||||
msgid "Enable if you want to in CRM abandoned shopping carts were unloaded"
|
||||
msgstr "Включите, если хотите, чтобы в CRM выгружались брошенные корзины покупателей"
|
||||
|
||||
msgid "firstName"
|
||||
msgstr "Имя"
|
||||
|
||||
msgid "lastName"
|
||||
msgstr "Фамилия"
|
||||
|
||||
msgid "phone"
|
||||
msgstr "Телефон"
|
||||
|
||||
msgid "tags"
|
||||
msgstr "Теги"
|
||||
|
||||
msgid "customerComment"
|
||||
msgstr "Комментарий клиента"
|
||||
|
||||
msgid "managerComment"
|
||||
msgstr "Комментарий менеджера"
|
||||
|
||||
msgid "email"
|
||||
msgstr "E-mail"
|
||||
|
||||
msgid "addressText"
|
||||
msgstr "Адрес"
|
||||
|
||||
msgid "addressCity"
|
||||
msgstr "Город"
|
||||
|
||||
msgid "addressIndex"
|
||||
msgstr "Индекс"
|
||||
|
||||
msgid "addressRegion"
|
||||
msgstr "Регион"
|
||||
|
||||
msgid "Standard CRM fields"
|
||||
msgstr "Стандартные поля CRM"
|
||||
|
||||
msgid "Warehouses available in CRM"
|
||||
msgstr "Склады, доступные в CRM"
|
||||
|
||||
msgid "Select warehouses to receive balances from CRM. To select several warehouses, hold down CTRL (for Windows and Linux) or ⌘ Command (for MacOS)"
|
||||
msgstr "Выберите склады для получения остатков из CRM. Для выбора нескольких складов зажмите CTRL (для Windows и Linux) или ⌘ Command (для MacOS)"
|
||||
|
||||
msgid "I agree to receive promotional newsletters"
|
||||
msgstr "Согласен на рекламно-информационные рассылки"
|
||||
|
||||
msgid "API key with one-shop access required"
|
||||
msgstr "Требуется API ключ с доступом к одному магазину"
|
||||
|
||||
msgid "The currency of the site differs from the currency of the store in CRM. For the integration to work correctly, the currencies in CRM and CMS must match"
|
||||
msgstr "Валюта сайта отличается от валюты магазина в CRM. Для корректной работы интеграции, валюты в CRM и CMS должны совпадать"
|
||||
|
||||
msgid "Loyalty program"
|
||||
msgstr "Программа лояльности"
|
||||
|
||||
msgid "Activate program loyalty"
|
||||
msgstr "Активировать программу лояльности"
|
||||
|
||||
msgid "Attention! When activating the loyalty program, the method of ICML catalog generation changes. Details in"
|
||||
msgstr "Внимание! При активации программы лояльности, изменяется способ генерации ICML каталога. Подробности в"
|
||||
|
||||
msgid "<a href='https://docs.simla.com/Users/Integration/SiteModules/WooCommerce/PLWoocommerce'>documentation loyalty program</a>"
|
||||
msgstr "<a href='https://docs.retailcrm.ru/Users/Integration/SiteModules/WooCommerce/PLWoocommerce#h-2'>документации программы лояльности</a>"
|
||||
|
||||
msgid "Terms of loyalty program"
|
||||
msgstr "Условия программы лояльности"
|
||||
|
||||
msgid "Insert the terms and conditions of the loyalty program"
|
||||
msgstr "Вставьте условия участия в программе лояльности"
|
||||
|
||||
msgid "Conditions of personal data processing"
|
||||
msgstr "Условия обработки персональных данных"
|
||||
|
||||
msgid "Insert the terms and conditions for processing personal data"
|
||||
msgstr "Вставьте условия обработки персональных данных"
|
||||
|
||||
msgid "To activate the loyalty program it is necessary to activate the <a href='?page=wc-settings'>'enable use of coupons option'</a>"
|
||||
msgstr "Для активации программы лояльности необходимо активировать опцию <a href='?page=wc-settings'>'включить использование купонов'</a>"
|
||||
|
||||
msgid "Bonus account"
|
||||
msgstr "Бонусный счёт"
|
||||
|
||||
msgid "Participation ID: "
|
||||
msgstr "ID участия: "
|
||||
|
||||
msgid "Current level: "
|
||||
msgstr "Текущий уровень: "
|
||||
|
||||
msgid "Bonuses on the account: "
|
||||
msgstr "Бонусов на счёте: "
|
||||
|
||||
msgid "Bonus card number: "
|
||||
msgstr "Номер бонусной карты: "
|
||||
|
||||
msgid "Date of registration: "
|
||||
msgstr "Дата регистрации: "
|
||||
|
||||
msgid "Current level rules"
|
||||
msgstr "Правила текущего уровня"
|
||||
|
||||
msgid "Required amount of purchases to move to the next level: "
|
||||
msgstr "Необходимая сумма покупок для перехода на следующий уровень: "
|
||||
|
||||
msgid "Activate participation in the loyalty program"
|
||||
msgstr "Активировать участие в программе лояльности"
|
||||
|
||||
msgid "Send"
|
||||
msgstr "Отправить"
|
||||
|
||||
msgid "To register in the loyalty program, fill in the form:"
|
||||
msgstr "Для регистрации в программе лояльности заполните форму:"
|
||||
|
||||
msgid " I agree with "
|
||||
msgstr " Я согласен с "
|
||||
|
||||
msgid "loyalty program terms"
|
||||
msgstr "условиями программы лояльности"
|
||||
|
||||
msgid "terms of personal data processing"
|
||||
msgstr "условиями обработки персональных данных"
|
||||
|
||||
msgid "Phone"
|
||||
msgstr "Телефон"
|
||||
|
||||
msgid "Error while registering in the loyalty program. Try again later"
|
||||
msgstr "Ошибка при регистрации в программе лояльности. Попробуйте позже"
|
||||
|
||||
msgid "The card is not linked"
|
||||
msgstr "Карта не привязана"
|
||||
|
||||
msgid "Error while retrieving data. Try again later"
|
||||
msgstr "Ошибка при получении данных. Попробуйте позже"
|
||||
|
||||
msgid "Error when activating the loyalty program. Try again later"
|
||||
msgstr "Ошибка при активации программы лояльности. Попробуйте позже"
|
||||
|
||||
msgid "Enter the correct phone number"
|
||||
msgstr "Введите корректный номер телефона"
|
||||
|
||||
msgid "Close"
|
||||
msgstr "Закрыть"
|
||||
|
||||
msgid "Ordinary products: accrual of 1 bonus for each %s %s"
|
||||
msgstr "Обычные товары: начисление 1 бонуса за каждые %s %s"
|
||||
|
||||
msgid "Promotional products: accrual of 1 bonus for each %s %s"
|
||||
msgstr "Акционные товары: начисление 1 бонуса за каждые %s %s"
|
||||
|
||||
msgid "Ordinary products: bonus accrual in the amount of %s%% of the purchase amount"
|
||||
msgstr "Обычные товары: начисление бонусов в размере %s%% от суммы покупки"
|
||||
|
||||
msgid "Promotional products: bonus accrual in the amount of %s%% of the purchase amount"
|
||||
msgstr "Акционные товары: начисление бонусов в размере %s%% от суммы покупки"
|
||||
|
||||
msgid "Ordinary products: %s%% discount"
|
||||
msgstr "Обычные товары: %s%% скидка"
|
||||
|
||||
msgid "Promotional products: %s%% discount"
|
||||
msgstr "Акционные товары: %s%% скидка"
|
||||
|
||||
msgid "Uploading services"
|
||||
msgstr "Выгрузка услуг"
|
||||
|
||||
msgid "Goods with the 'virtual' option enabled will be uploaded to Simla as services"
|
||||
msgstr "Товары с включенной опцией 'виртуальные' будут выгружаться в CRM как услуги"
|
||||
|
||||
msgid "User not found in the system"
|
||||
msgstr "Пользователь не найден в системе"
|
||||
|
||||
msgid "Error when searching for participation in loyalty programs"
|
||||
msgstr "Ошибка при поиске участия в программах лояльности"
|
||||
|
||||
msgid "No active participation in the loyalty program was detected"
|
||||
msgstr "Не обнаружено активного участия в программе лояльности"
|
||||
|
||||
msgid "No bonuses for debiting"
|
||||
msgstr "Нет бонусов для списания"
|
||||
|
||||
msgid "Loyalty program not found"
|
||||
msgstr "Программа лояльности не найдена"
|
||||
|
||||
msgid "Loyalty program is not active"
|
||||
msgstr "Программа лояльности не активна"
|
||||
|
||||
msgid "Loyalty program blocked"
|
||||
msgstr "Программа лояльности заблокирована"
|
||||
|
||||
msgid "This user is a corporate person"
|
||||
msgstr "Данный пользователь является корпоратиным лицом"
|
||||
|
||||
msgid "It is possible to write off"
|
||||
msgstr "Возможно списать"
|
||||
|
||||
msgid "bonuses"
|
||||
msgstr "бонусов"
|
||||
|
||||
msgid "Use coupon:"
|
||||
msgstr "Используйте купон:"
|
||||
|
||||
msgid "Points will be awarded upon completion of the order:"
|
||||
msgstr "По завершению заказа будет начислено баллов:"
|
||||
|
||||
msgid "Unloading promotional prices of offers"
|
||||
msgstr "Выгрузка акционных цен торговых предложений"
|
||||
|
||||
msgid "Every 4 hours"
|
||||
msgstr "Каждые 4 часа"
|
||||
|
||||
msgid "Upload prices now"
|
||||
msgstr "Выгрузить цены сейчас"
|
||||
|
||||
msgid "Uploaded discount price"
|
||||
msgstr "Выгрузка акционных цен"
|
||||
|
||||
msgid "This functionality loads the promotional prices offers into Simla.com"
|
||||
msgstr "Эта функция загружает акционные цены торговых предложений в Simla.com"
|
||||
|
||||
msgid "Promotional prices unloaded"
|
||||
msgstr "Акционные цены выгружены"
|
||||
|
||||
msgid "Woocommerce promotional price"
|
||||
msgstr "Акционная цена Woocommerce"
|
||||
|
||||
msgid "Promotional price type for Woocommerce store, generated automatically. Necessary for correct synchronization work when loyalty program is enabled (Do not delete. Do not deactivate)"
|
||||
msgstr "Акционный тип цены для магазина Woocommerce, сгенерированный автоматически. Необходим для корректной работы синхронизации при включенной программы лояльности (Не удалять. Не деактивировать)"
|
3
src/assets/css/debug-info.css
Normal file
3
src/assets/css/debug-info.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.retail-cron-info-title {
|
||||
font-weight: bold;
|
||||
}
|
1
src/assets/css/debug-info.min.css
vendored
Normal file
1
src/assets/css/debug-info.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.retail-cron-info-title{font-weight:700}
|
59
src/assets/css/meta-fields.css
Normal file
59
src/assets/css/meta-fields.css
Normal file
|
@ -0,0 +1,59 @@
|
|||
.retailcrm-meta-select {
|
||||
margin-right: 25px !important;
|
||||
float:left !important;
|
||||
width: 200px !important;
|
||||
text-align-last: center;
|
||||
border: 2px solid #2271b1;
|
||||
}
|
||||
|
||||
.retailcrm-select-pair {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.retailcrm-order-label, .retailcrm-customer-label {
|
||||
display: block;
|
||||
font-size: 14px !important;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.add-new-select-retailcrm {
|
||||
display:block;
|
||||
width: 200px !important;
|
||||
height: 34px;
|
||||
color: #4169e1;
|
||||
border-color: #4169e1;
|
||||
background: #f6f7f7;
|
||||
vertical-align: top;
|
||||
margin-top: 16px;
|
||||
margin-left: 128px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.delete-select-retailcrm {
|
||||
display: block;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
--weight: 0.5px;
|
||||
--aa: 0.5px; /* anti-aliasing */
|
||||
--color: #4169e1;
|
||||
border-color: #4169e1;
|
||||
background: #f6f7f7;
|
||||
padding: 0;
|
||||
background:
|
||||
linear-gradient(45deg, transparent calc(50% - var(--weight) - var(--aa)), var(--color) calc(50% - var(--weight)), var(--color) calc(50% + var(--weight)), transparent calc(50% + var(--weight) + var(--aa))),
|
||||
linear-gradient(-45deg, transparent calc(50% - var(--weight) - var(--aa)), var(--color) calc(50% - var(--weight)), var(--color) calc(50% + var(--weight)), transparent calc(50% + var(--weight) + var(--aa)));
|
||||
}
|
||||
|
||||
|
||||
.red-selected-retailcrm {
|
||||
border: solid 3px red !important;
|
||||
animation: 2s blinker linear infinite;
|
||||
#transition: border-width 0.6s linear;
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
0% { opacity: 1.0; }
|
||||
50% { opacity: 0.3; }
|
||||
100% { opacity: 1.0; }
|
||||
}
|
1
src/assets/css/meta-fields.min.css
vendored
Normal file
1
src/assets/css/meta-fields.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.retailcrm-meta-select{margin-right:25px!important;float:left!important;width:200px!important;text-align-last:center;border:2px solid #2271b1}.retailcrm-select-pair{margin-bottom:30px}.retailcrm-customer-label,.retailcrm-order-label{display:block;font-size:14px!important;margin-top:16px;margin-bottom:10px}.add-new-select-retailcrm{display:block;width:200px!important;height:34px;color:#4169e1;border-color:#4169e1;background:#f6f7f7;vertical-align:top;margin-top:16px;margin-left:128px;margin-bottom:10px}.delete-select-retailcrm{display:block;width:34px;height:34px;--weight:0.5px;--aa:0.5px;--color:#4169e1;border-color:#4169e1;background:#f6f7f7;padding:0;background:linear-gradient(45deg,transparent calc(50% - var(--weight) - var(--aa)),var(--color) calc(50% - var(--weight)),var(--color) calc(50% + var(--weight)),transparent calc(50% + var(--weight) + var(--aa))),linear-gradient(-45deg,transparent calc(50% - var(--weight) - var(--aa)),var(--color) calc(50% - var(--weight)),var(--color) calc(50% + var(--weight)),transparent calc(50% + var(--weight) + var(--aa)))}.red-selected-retailcrm{border:solid 3px red!important;animation:2s blinker linear infinite}@keyframes blinker{0%{opacity:1}50%{opacity:.3}100%{opacity:1}}
|
33
src/assets/css/progress-bar.css
Normal file
33
src/assets/css/progress-bar.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
.retail-progress {
|
||||
border-radius: 18px;
|
||||
border: 1px solid rgba(122, 122, 122, 0.15);
|
||||
width: 400px;
|
||||
height: 18px;
|
||||
overflow: hidden;
|
||||
transition: height 0.25s ease;
|
||||
}
|
||||
|
||||
.retail-progress__loader {
|
||||
width: 0;
|
||||
border-radius: 18px;
|
||||
background: #4169e1;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 0 30px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
transition: width 0.4s ease-in;
|
||||
line-height: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.retailcrm-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.retail-count-data-upload {
|
||||
margin-bottom: 20px;
|
||||
size: 30px;
|
||||
color: #4169e1;
|
||||
font-weight: bold;
|
||||
}
|
5
src/assets/css/progress-bar.min.css
vendored
Normal file
5
src/assets/css/progress-bar.min.css
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
<<<<<<< HEAD
|
||||
.retail-progress{border-radius:18px;border:1px solid rgba(122,122,122,.15);width:400px;height:18px;overflow:hidden;transition:height .25s ease}.retail-progress__loader{width:0;border-radius:18px;background:#4169e1;color:#fff;text-align:center;padding:0 30px;font-size:18px;font-weight:600;transition:width .4s ease-in;line-height:18px;box-sizing:border-box}.retail-hidden{display:none!important}.retail-count-data-upload{margin-bottom:20px;size:30px;color:#4169e1;font-weight:700}
|
||||
=======
|
||||
.retail-progress{border-radius:18px;border:1px solid rgba(122,122,122,.15);width:400px;height:18px;overflow:hidden;transition:height .25s ease}.retail-progress__loader{width:0;border-radius:18px;background:#4169e1;color:#fff;text-align:center;padding:0 30px;font-size:18px;font-weight:600;transition:width .4s ease-in;line-height:18px;box-sizing:border-box}.retailcrm-hidden{display:none!important}.retail-count-data-upload{margin-bottom:20px;size:30px;color:#4169e1;font-weight:700}
|
||||
>>>>>>> Add mapping metadata fields in settings
|
34
src/assets/css/retailcrm-loyalty-style.css
Normal file
34
src/assets/css/retailcrm-loyalty-style.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
.popup-fade-loyalty {
|
||||
display: none;
|
||||
}
|
||||
.popup-fade-loyalty:before {
|
||||
content: '';
|
||||
background: #000;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.7;
|
||||
z-index: 9999;
|
||||
}
|
||||
.popup-loyalty {
|
||||
position: fixed;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
padding: 20px;
|
||||
width: 1000px;
|
||||
margin-left: -500px;
|
||||
height: 50%;
|
||||
background: #fff;
|
||||
border: 1px solid orange;
|
||||
border-radius: 4px;
|
||||
z-index: 99999;
|
||||
opacity: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
.popup-close-loyalty {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
34
src/assets/css/whatsapp-icon.css
Normal file
34
src/assets/css/whatsapp-icon.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
.whatsapp-icon {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
left: 0;
|
||||
bottom: 32px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.whatsapp-icon_left {
|
||||
left: 32px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.whatsapp-icon_right {
|
||||
right: 32px;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.whatsapp-icon__icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat-btn__text {
|
||||
color: #8A96A6;
|
||||
font-weight: 600;
|
||||
font-size: 8px;
|
||||
line-height: 10px;
|
||||
text-align: center;
|
||||
margin: 0 0 8px;
|
||||
white-space: nowrap;
|
||||
}
|
1
src/assets/css/whatsapp-icon.min.css
vendored
Normal file
1
src/assets/css/whatsapp-icon.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.whatsapp-icon{position:fixed;z-index: 999;left:0;bottom:32px;width:60px;height:60px;box-sizing:border-box}.whatsapp-icon_left{left:32px;right:auto}.whatsapp-icon_right{right:32px;left:auto}.whatsapp-icon__icon{width:100%;height:100%}.chat-btn__text{color:#8A96A6;font-weight:600;font-size:8px;line-height:10px;text-align:center;margin:0 0 8px;white-space:nowrap}
|
178
src/assets/default/default_meta_fields.txt
Normal file
178
src/assets/default/default_meta_fields.txt
Normal file
|
@ -0,0 +1,178 @@
|
|||
_backorders
|
||||
_billing_address_1
|
||||
_billing_address_2
|
||||
_billing_address_index
|
||||
_billing_city
|
||||
_billing_company
|
||||
_billing_country
|
||||
_billing_email
|
||||
_billing_first_name
|
||||
_billing_last_name
|
||||
_billing_phone
|
||||
_billing_postcode
|
||||
_billing_state
|
||||
_button_text
|
||||
_cart_discount
|
||||
_cart_discount_tax
|
||||
_cart_hash
|
||||
_children
|
||||
_completed_date
|
||||
_created_via
|
||||
_customer_ip_address
|
||||
_customer_user
|
||||
_customer_user_agent
|
||||
_date_completed
|
||||
_date_paid
|
||||
_download_expiry
|
||||
_download_limit
|
||||
_download_permissions_granted
|
||||
_downloadable
|
||||
_downloadable_files
|
||||
_edit_last
|
||||
_edit_lock
|
||||
_manage_stock
|
||||
_new_order_email_sent
|
||||
_order_currency
|
||||
_order_key
|
||||
_order_shipping
|
||||
_order_shipping_tax
|
||||
_order_stock_reduced
|
||||
_order_tax
|
||||
_order_total
|
||||
_order_version
|
||||
_paid_date
|
||||
_payment_method
|
||||
_payment_method_title
|
||||
_price
|
||||
_prices_include_tax
|
||||
_product_attributes
|
||||
_product_image_gallery
|
||||
_product_url
|
||||
_product_version
|
||||
_recorded_coupon_usage_counts
|
||||
_recorded_sales
|
||||
_refund_amount
|
||||
_refund_reason
|
||||
_refunded_by
|
||||
_refunded_payment
|
||||
_regular_price
|
||||
_sale_price
|
||||
_shipping_address_1
|
||||
_shipping_address_2
|
||||
_shipping_address_index
|
||||
_shipping_city
|
||||
_shipping_company
|
||||
_shipping_country
|
||||
_shipping_first_name
|
||||
_shipping_last_name
|
||||
_shipping_postcode
|
||||
_shipping_state
|
||||
_sku
|
||||
_sold_individually
|
||||
_stock
|
||||
_stock_status
|
||||
_stripe_charge_captured
|
||||
_stripe_currency
|
||||
_stripe_customer_id
|
||||
_stripe_fee
|
||||
_stripe_intent_id
|
||||
_stripe_net
|
||||
_stripe_source_id
|
||||
_tax_class
|
||||
_tax_status
|
||||
_thumbnail_id
|
||||
_transaction_id
|
||||
_variation_description
|
||||
_virtual
|
||||
_wc_attachment_source
|
||||
_wc_average_rating
|
||||
_wc_review_count
|
||||
_wp_attached_file
|
||||
_wp_attachment_metadata
|
||||
_wp_desired_post_slug
|
||||
_wp_old_slug
|
||||
_wp_page_template
|
||||
_wp_trash_meta_comments_status
|
||||
_wp_trash_meta_status
|
||||
_wp_trash_meta_time
|
||||
_wpcom_is_markdown
|
||||
_used_by
|
||||
attribute_logo
|
||||
attribute_pa_color
|
||||
attribute_pa_size
|
||||
total_sales
|
||||
coupon_amount
|
||||
date_expires
|
||||
discount_type
|
||||
exclude_sale_items
|
||||
free_shipping
|
||||
individual_use
|
||||
is_vat_exempt
|
||||
limit_usage_to_x_items
|
||||
usage_count
|
||||
usage_limit
|
||||
usage_limit_per_user
|
||||
_shipping_phone
|
||||
|
||||
## Customer
|
||||
|
||||
_last_order
|
||||
_order_count
|
||||
_woocommerce_persistent_cart_1
|
||||
_woocommerce_tracks_anon_id
|
||||
admin_color
|
||||
billing_address_1
|
||||
billing_address_2
|
||||
billing_city
|
||||
billing_company
|
||||
billing_country
|
||||
billing_email
|
||||
billing_first_name
|
||||
billing_last_name
|
||||
billing_phone
|
||||
billing_postcode
|
||||
billing_state
|
||||
comment_shortcuts
|
||||
community-events-location
|
||||
default_password_nag
|
||||
description
|
||||
dismissed_maxmind_license_key_notice
|
||||
dismissed_no_secure_connection_notice
|
||||
dismissed_regenerating_thumbnails_notice
|
||||
dismissed_update_notice
|
||||
dismissed_wc_admin_notice
|
||||
dismissed_wp_pointers
|
||||
first_name
|
||||
last_name
|
||||
last_update
|
||||
locale
|
||||
paying_customer
|
||||
rich_editing
|
||||
session_tokens
|
||||
shipping_address_1
|
||||
shipping_address_2
|
||||
shipping_city
|
||||
shipping_company
|
||||
shipping_country
|
||||
shipping_first_name
|
||||
shipping_last_name
|
||||
shipping_method
|
||||
shipping_phone
|
||||
shipping_postcode
|
||||
shipping_state
|
||||
show_admin_bar_front
|
||||
show_welcome_panel
|
||||
syntax_highlighting
|
||||
use_ssl
|
||||
wc_last_active
|
||||
woocommerce_admin_activity_panel_inbox_last_read
|
||||
wp_capabilities
|
||||
wp_dashboard_quick_press_last_post_id
|
||||
wp_product_import_error_log
|
||||
wp_user_level
|
||||
wp_user-settings
|
||||
wp_user-settings-time
|
||||
wp_woocommerce_product_import_mapping
|
||||
closedpostboxes_shop_order
|
||||
metaboxhidden_shop_order
|
||||
wp__stripe_customer_id
|
82
src/assets/js/retailcrm-cron-info.js
Normal file
82
src/assets/js/retailcrm-cron-info.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
jQuery(function () {
|
||||
function RetailcrmCronInfo()
|
||||
{
|
||||
this.title = jQuery('.debug_info_options').get(0)
|
||||
this.submitButton = jQuery('button[id="clear_cron_tasks"]').get(0);
|
||||
|
||||
if (typeof this.title === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof this.submitButton === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.icml = 0;
|
||||
this.history = 0;
|
||||
this.inventories = 0;
|
||||
this.messageSuccessful = '';
|
||||
this.loyaltyUploadPrice = 0;
|
||||
|
||||
this.adminUrl = AdminUrl.url;
|
||||
|
||||
let _this = this;
|
||||
|
||||
jQuery.ajax({
|
||||
url: this.adminUrl + '/admin-ajax.php?action=cron_info',
|
||||
method: "POST",
|
||||
timeout: 0,
|
||||
data: {ajax: 1},
|
||||
dataType: "json"
|
||||
})
|
||||
.done(function (response) {
|
||||
_this.history = response.history;
|
||||
_this.icml = response.icml;
|
||||
_this.inventories = response.inventories;
|
||||
_this.messageSuccessful = response.translate.tr_successful;
|
||||
_this.loyaltyUploadPrice = response.loyaltyUploadPrice
|
||||
|
||||
_this.displayInfoAboutCron(
|
||||
response.translate.tr_td_cron,
|
||||
response.translate.tr_td_icml,
|
||||
response.translate.tr_td_history,
|
||||
response.translate.tr_td_inventories,
|
||||
response.translate.tr_td_loyaltyUploadPrice
|
||||
);
|
||||
})
|
||||
|
||||
this.clearCronTasks = this.clearCronTasks.bind(this);
|
||||
|
||||
jQuery(this.submitButton).click(this.clearCronTasks);
|
||||
}
|
||||
|
||||
RetailcrmCronInfo.prototype.displayInfoAboutCron = function (cron, icml, history, inventories, loyaltyUploadPrice) {
|
||||
this.table = jQuery(this.title).next();
|
||||
this.table.append('<tbody class="retail-debug-info"></tbody>');
|
||||
this.infoTable = jQuery('tbody[class="retail-debug-info"]').get(0);
|
||||
|
||||
jQuery(this.infoTable).append("<tr><td class='retail-cron-info-title'>" + cron + " : " + "</td></tr>");
|
||||
jQuery(this.infoTable).append("<tr><td>" + icml + "</td><td> " + this.icml + "</td></tr>");
|
||||
jQuery(this.infoTable).append("<tr><td>" + history + "</td><td> " + this.history + "</td></tr>");
|
||||
jQuery(this.infoTable).append("<tr><td>" + inventories + "</td><td> " + this.inventories + "</td></tr>");
|
||||
jQuery(this.infoTable).append("<tr><td>" + loyaltyUploadPrice + "</td><td>" + this.loyaltyUploadPrice + "</td></tr>");
|
||||
}
|
||||
|
||||
RetailcrmCronInfo.prototype.clearCronTasks = function () {
|
||||
let _this = this;
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: this.adminUrl + '/admin-ajax.php?action=clear_cron_tasks',
|
||||
success: function (response) {
|
||||
alert(_this.messageSuccessful);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.RetailcrmCronInfo = RetailcrmCronInfo;
|
||||
|
||||
if (!(typeof RetailcrmCronInfo === 'undefined')) {
|
||||
new window.RetailcrmCronInfo();
|
||||
}
|
||||
});
|
186
src/assets/js/retailcrm-export.js
Normal file
186
src/assets/js/retailcrm-export.js
Normal file
|
@ -0,0 +1,186 @@
|
|||
jQuery(function () {
|
||||
function RetailcrmExportForm()
|
||||
{
|
||||
this.submitButton = jQuery('button[id="export-orders-submit"]').get(0);
|
||||
this.selectedOrdersButton = jQuery('button[id="export_selected_orders_btn"]').get(0);
|
||||
|
||||
jQuery(this.submitButton).after('<div id="export-orders-progress" class="retail-progress retailcrm-hidden"></div');
|
||||
jQuery(this.submitButton).before('<div id="export-orders-count" class="retail-count-data-upload"></div');
|
||||
|
||||
this.progressBar = jQuery('div[id="export-orders-progress"]').get(0);
|
||||
|
||||
if (typeof this.submitButton === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof this.selectedOrdersButton === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof this.progressBar === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
jQuery(this.submitButton).addClass('retailcrm-hidden');
|
||||
|
||||
this.ordersCount = 0;
|
||||
this.customersCount = 0;
|
||||
|
||||
this.adminUrl = AdminUrl.url;
|
||||
|
||||
let _this = this;
|
||||
|
||||
jQuery.ajax({
|
||||
url: this.adminUrl + '/admin-ajax.php?action=content_upload',
|
||||
method: "POST",
|
||||
timeout: 0,
|
||||
data: {ajax: 1},
|
||||
dataType: "json"
|
||||
})
|
||||
.done(function (response) {
|
||||
_this.ordersCount = Number(response.count_orders);
|
||||
_this.customersCount = Number(response.count_users);
|
||||
jQuery(_this.submitButton).removeClass('retailcrm-hidden');
|
||||
|
||||
|
||||
_this.messageEmtyField = response.translate.tr_empty_field;
|
||||
_this.messageSuccessful = response.translate.tr_successful;
|
||||
|
||||
_this.displayCountUploadData(response.translate.tr_order, response.translate.tr_customer);
|
||||
})
|
||||
|
||||
this.isDone = false;
|
||||
this.ordersStepSize = 50;
|
||||
this.customersStepSize = 50;
|
||||
this.ordersStep = 0;
|
||||
this.customersStep = 0;
|
||||
this.displayCountUploadData = this.displayCountUploadData.bind(this);
|
||||
this.submitAction = this.submitAction.bind(this);
|
||||
this.actionExportSelectedOrders = this.actionExportSelectedOrders.bind(this);
|
||||
this.exportAction = this.exportAction.bind(this);
|
||||
this.exportDone = this.exportDone.bind(this);
|
||||
this.initializeProgressBar = this.initializeProgressBar.bind(this);
|
||||
this.updateProgressBar = this.updateProgressBar.bind(this);
|
||||
|
||||
jQuery(this.submitButton).click(this.submitAction);
|
||||
jQuery(this.selectedOrdersButton).click(this.actionExportSelectedOrders);
|
||||
}
|
||||
|
||||
RetailcrmExportForm.prototype.displayCountUploadData = function (order, customer) {
|
||||
this.counter = jQuery('div[id="export-orders-count"]').get(0);
|
||||
|
||||
|
||||
jQuery(this.counter).text(`${customer}: ${this.customersCount} ${order}: ${this.ordersCount}`);
|
||||
}
|
||||
|
||||
RetailcrmExportForm.prototype.submitAction = function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
jQuery(this.counter).css("margin-left", "120px");
|
||||
|
||||
this.initializeProgressBar();
|
||||
this.exportAction();
|
||||
};
|
||||
|
||||
RetailcrmExportForm.prototype.exportAction = function () {
|
||||
|
||||
let data = {};
|
||||
|
||||
if (this.customersStep * this.customersStepSize < this.customersCount) {
|
||||
data.Step = this.customersStep;
|
||||
data.Entity = 'customer';
|
||||
this.customersStep++;
|
||||
} else {
|
||||
if (this.ordersStep * this.ordersStepSize < this.ordersCount) {
|
||||
data.Step = this.ordersStep;
|
||||
data.Entity = 'order';
|
||||
this.ordersStep++;
|
||||
} else {
|
||||
data.RETAILCRM_UPDATE_SINCE_ID = 1;
|
||||
this.isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
let _this = this;
|
||||
|
||||
jQuery.ajax({
|
||||
url: this.adminUrl + '/admin-ajax.php?action=do_upload',
|
||||
method: "POST",
|
||||
timeout: 0,
|
||||
data: data
|
||||
})
|
||||
.done(function (response) {
|
||||
if (_this.isDone) {
|
||||
return _this.exportDone();
|
||||
}
|
||||
|
||||
_this.updateProgressBar();
|
||||
_this.exportAction();
|
||||
})
|
||||
};
|
||||
|
||||
RetailcrmExportForm.prototype.initializeProgressBar = function () {
|
||||
jQuery(this.submitButton).addClass('retailcrm-hidden');
|
||||
jQuery(this.progressBar)
|
||||
.removeClass('retailcrm-hidden')
|
||||
.append(jQuery('<div/>', {class: 'retail-progress__loader', text: '0%'}))
|
||||
|
||||
window.addEventListener('beforeunload', this.confirmLeave);
|
||||
};
|
||||
|
||||
RetailcrmExportForm.prototype.updateProgressBar = function () {
|
||||
let processedOrders = this.ordersStep * this.ordersStepSize;
|
||||
|
||||
if (processedOrders > this.ordersCount) {
|
||||
processedOrders = this.ordersCount;
|
||||
}
|
||||
|
||||
let processedCustomers = this.customersStep * this.customersStepSize;
|
||||
|
||||
if (processedCustomers > this.customersCount) {
|
||||
processedCustomers = this.customersCount;
|
||||
}
|
||||
|
||||
const processed = processedOrders + processedCustomers;
|
||||
const total = this.ordersCount + this.customersCount;
|
||||
const percents = Math.round(100 * processed / total);
|
||||
|
||||
jQuery(this.progressBar).find('.retail-progress__loader').text(percents + '%');
|
||||
jQuery(this.progressBar).find('.retail-progress__loader').css('width', percents + '%');
|
||||
jQuery(this.progressBar).find('.retail-progress__loader').attr('title', processed + '/' + total);
|
||||
};
|
||||
|
||||
RetailcrmExportForm.prototype.confirmLeave = function (event) {
|
||||
event.preventDefault();
|
||||
event.returnValue = 'Export process has been started';
|
||||
}
|
||||
|
||||
RetailcrmExportForm.prototype.exportDone = function () {
|
||||
window.removeEventListener('beforeunload', this.confirmLeave);
|
||||
alert(this.messageSuccessful);
|
||||
}
|
||||
|
||||
RetailcrmExportForm.prototype.actionExportSelectedOrders = function () {
|
||||
let ids = jQuery('#woocommerce_integration-retailcrm_export_selected_orders_ids').val();
|
||||
|
||||
if (ids === '') {
|
||||
alert(this.messageEmtyField);
|
||||
} else {
|
||||
let _this = this;
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: this.adminUrl + '/admin-ajax.php?action=upload_selected_orders&order_ids_retailcrm=' + ids,
|
||||
success: function (response) {
|
||||
alert(_this.messageSuccessful);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.RetailcrmExportForm = RetailcrmExportForm;
|
||||
|
||||
if (!(typeof RetailcrmExportForm === 'undefined')) {
|
||||
new window.RetailcrmExportForm();
|
||||
}
|
||||
});
|
121
src/assets/js/retailcrm-loyalty-actions.js
Normal file
121
src/assets/js/retailcrm-loyalty-actions.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
jQuery(function() {
|
||||
jQuery('#loyaltyRegisterForm').on("submit", function (event) {
|
||||
var termsCheck = jQuery('#termsLoyalty');
|
||||
var privacyCheck = jQuery('#privacyLoyalty');
|
||||
|
||||
if (termsCheck.length) {
|
||||
if (!termsCheck.is(':checked')) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (privacyCheck.length) {
|
||||
if (!privacyCheck.is(':checked')) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let phone = jQuery('#phoneLoyalty');
|
||||
|
||||
if (!phone.val().match(/(?:\+|\d)[\d\-\(\) ]{7,}\d/)) {
|
||||
|
||||
if (!jQuery('#warningLoyaltyPhone').length) {
|
||||
phone.parent().append('<span style="color: red" id="warningLoyaltyPhone">' + messagePhone + '</span>')
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
return false;
|
||||
} else {
|
||||
jQuery('#warningLoyaltyPhone').remove();
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
url: loyaltyUrl.url + '/admin-ajax.php?action=register_customer_loyalty',
|
||||
method: 'POST',
|
||||
timeout: 0,
|
||||
data: {ajax: 1, phone: phone.val(), userId: customerId},
|
||||
dataType: 'json'
|
||||
})
|
||||
.done(function (response) {
|
||||
if (response.hasOwnProperty('error')) {
|
||||
jQuery('#loyaltyRegisterForm').append('<p style="color: red">'+ response.error + '</p>')
|
||||
|
||||
event.preventDefault();
|
||||
return false;
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
jQuery('#loyaltyActivateForm').on("submit", function (event) {
|
||||
var activateCheckbox = jQuery('#loyaltyActiveCheckbox');
|
||||
|
||||
if (activateCheckbox.length) {
|
||||
if (!activateCheckbox.is(':checked')) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
url: loyaltyUrl.url + '/admin-ajax.php?action=activate_customer_loyalty',
|
||||
method: 'POST',
|
||||
timeout: 0,
|
||||
data: {ajax: 1, loyaltyId: loyaltyId},
|
||||
dataType: 'json'
|
||||
})
|
||||
.done(function (response) {
|
||||
if (response.hasOwnProperty('error')) {
|
||||
jQuery('#loyaltyRegisterForm').append('<p style="color: red">'+ response.error + '</p>')
|
||||
|
||||
event.preventDefault();
|
||||
return false;
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
jQuery('.popup-open-loyalty').click(function() {
|
||||
if (jQuery(this).attr('id') === 'terms-popup') {
|
||||
jQuery('#popup-loyalty-text').html(termsLoyalty);
|
||||
} else {
|
||||
jQuery('#popup-loyalty-text').html(privacyLoyalty);
|
||||
}
|
||||
|
||||
jQuery('.popup-fade-loyalty').fadeIn();
|
||||
return false;
|
||||
});
|
||||
|
||||
jQuery('.popup-close-loyalty').click(function() {
|
||||
jQuery(this).parents('.popup-fade-loyalty').fadeOut();
|
||||
return false;
|
||||
});
|
||||
|
||||
jQuery(document).keydown(function(e) {
|
||||
if (e.keyCode === 27) { // Key Escape
|
||||
e.stopPropagation();
|
||||
jQuery('.popup-fade-loyalty').fadeOut();
|
||||
}
|
||||
});
|
||||
|
||||
jQuery('.popup-fade-loyalty').click(function(e) {
|
||||
if (jQuery(e.target).closest('.popup-loyalty').length == 0) {
|
||||
jQuery(this).fadeOut();
|
||||
}
|
||||
});
|
||||
|
||||
jQuery('#phoneLoyalty').keydown(function (e) {
|
||||
let key = e.key;
|
||||
|
||||
return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')'|| key == '-' ||
|
||||
key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace';
|
||||
});
|
||||
});
|
6
src/assets/js/retailcrm-loyalty-cart.js
Normal file
6
src/assets/js/retailcrm-loyalty-cart.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
function inputLoyaltyCode() {
|
||||
let couponInput = document.getElementById('coupon_code');
|
||||
let couponCode = document.getElementById('input_loyalty_code');
|
||||
|
||||
couponInput.value = couponCode.innerText;
|
||||
}
|
36
src/assets/js/retailcrm-loyalty.js
Normal file
36
src/assets/js/retailcrm-loyalty.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
jQuery(function() {
|
||||
if (jQuery('#woocommerce_integration-retailcrm_loyalty').is(':checked')) {
|
||||
checkActiveCoupon();
|
||||
}
|
||||
|
||||
jQuery('#woocommerce_integration-retailcrm_loyalty').change(function () {
|
||||
if (this.checked) {
|
||||
checkActiveCoupon();
|
||||
}
|
||||
})
|
||||
|
||||
function checkActiveCoupon()
|
||||
{
|
||||
jQuery.ajax({
|
||||
url: AdminUrl.url + '/admin-ajax.php?action=get_status_coupon',
|
||||
method: 'POST',
|
||||
timeout: 0,
|
||||
data: {ajax: 1},
|
||||
dataType: 'json'
|
||||
})
|
||||
.done(function (response) {
|
||||
if (response.coupon_status !== 'yes') {
|
||||
var checkElement = jQuery('#woocommerce_integration-retailcrm_loyalty');
|
||||
checkElement.parent().css('color', 'red');
|
||||
checkElement.css('border-color', 'red');
|
||||
checkElement.prop('checked', false);
|
||||
|
||||
if (!jQuery('#coupon_warning').length) {
|
||||
checkElement.parent().parent().append(
|
||||
"<p id='coupon_warning' style='color: red'>" + response.translate.coupon_warning + "</p>"
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
335
src/assets/js/retailcrm-meta-fields.js
Normal file
335
src/assets/js/retailcrm-meta-fields.js
Normal file
|
@ -0,0 +1,335 @@
|
|||
jQuery(function () {
|
||||
function RetailcrmMetaFields()
|
||||
{
|
||||
jQuery('.order-meta-data-retailcrm').closest('tr').addClass('retailcrm-hidden')
|
||||
jQuery('.customer-meta-data-retailcrm').closest('tr').addClass('retailcrm-hidden')
|
||||
|
||||
jQuery('.customer-meta-data-retailcrm').closest('tr').after('<tr class="retailcrm-insert-select"></tr>')
|
||||
this.insertSelects = jQuery('.retailcrm-insert-select').get(0);
|
||||
this.orderTextArea = jQuery('.order-meta-data-retailcrm').get(0);
|
||||
this.customerTextArea = jQuery('.customer-meta-data-retailcrm').get(0);
|
||||
this.indexOrder = 1;
|
||||
this.indexCustomer = 1;
|
||||
|
||||
if (typeof this.insertSelects === 'undefined'
|
||||
|| this.orderTextArea === 'undefined'
|
||||
|| this.customerTextArea === 'undefined'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jQuery(this.insertSelects).append(
|
||||
`<label class="retailcrm-order-label">Custom fields for order</label><div class="retailcrm-order-selects"></div>` +
|
||||
`<label class="retailcrm-customer-label">Custom fields for customer</label><div class="retailcrm-customer-selects"></div>` +
|
||||
`<button class="add-new-select-retailcrm" id="add-new-select-order-retailcrm">Add new select for order</button>` +
|
||||
`<button class="add-new-select-retailcrm" id="add-new-select-customer-retailcrm">Add new select for customer</button>`
|
||||
);
|
||||
|
||||
let _this = this;
|
||||
|
||||
if (this.orderTextArea.value === '') {
|
||||
this.orderTextArea.value = JSON.stringify({});
|
||||
}
|
||||
|
||||
if (this.customerTextArea.value === '') {
|
||||
this.customerTextArea.value = JSON.stringify({});
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
url: AdminUrl.url + '/admin-ajax.php?action=set_meta_fields',
|
||||
method: 'POST',
|
||||
timeout: 0,
|
||||
data: {ajax: 1},
|
||||
dataType: 'json'
|
||||
})
|
||||
.done(function (response) {
|
||||
_this.dataFields = response;
|
||||
|
||||
jQuery(".retailcrm-order-label").text(response.translate.tr_lb_order);
|
||||
jQuery(".retailcrm-customer-label").text(response.translate.tr_lb_customer);
|
||||
jQuery(".add-new-select-retailcrm").text(response.translate.tr_btn);
|
||||
jQuery(".add-new-select-retailcrm").text(response.translate.tr_btn);
|
||||
|
||||
if (jQuery.isEmptyObject(JSON.parse(_this.orderTextArea.value)) && jQuery.isEmptyObject(JSON.parse(_this.customerTextArea.value))) {
|
||||
_this.createSelects(_this.dataFields);
|
||||
}
|
||||
|
||||
if (!jQuery.isEmptyObject(JSON.parse(_this.orderTextArea.value))) {
|
||||
let objectJson = JSON.parse(_this.orderTextArea.value);
|
||||
|
||||
_this.indexOrder = _this.restoreSelectsData(
|
||||
objectJson,
|
||||
_this.dataFields.order,
|
||||
'order',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
if (!jQuery.isEmptyObject(JSON.parse(_this.customerTextArea.value))) {
|
||||
let objectJson = JSON.parse(_this.customerTextArea.value);
|
||||
|
||||
_this.indexCustomer = _this.restoreSelectsData(
|
||||
objectJson,
|
||||
_this.dataFields.customer,
|
||||
'customer',
|
||||
1
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
jQuery(this.insertSelects).on('change', '.retailcrm-meta-select', function() {
|
||||
let selectsData = {};
|
||||
let typeSelect = this.id.split('-')[0]
|
||||
let idChangeSelect = this.id.split('-')[2]
|
||||
|
||||
if (this.id.includes('order')) {
|
||||
selectsData = _this.getSelectsData(
|
||||
_this.orderTextArea.value,
|
||||
'order',
|
||||
idChangeSelect,
|
||||
typeSelect
|
||||
);
|
||||
} else {
|
||||
selectsData = _this.getSelectsData(
|
||||
_this.customerTextArea.value,
|
||||
'customer',
|
||||
idChangeSelect,
|
||||
typeSelect
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(selectsData).length != 0) {
|
||||
if (this.id.includes('order')) {
|
||||
_this.orderTextArea.value = JSON.stringify(selectsData);
|
||||
} else {
|
||||
_this.customerTextArea.value = JSON.stringify(selectsData);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
jQuery(this.insertSelects).on('click', '.add-new-select-retailcrm', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.id.includes('order')) {
|
||||
_this.addPairSelects(
|
||||
'.retailcrm-order-selects',
|
||||
'order',
|
||||
_this.dataFields.order,
|
||||
_this.indexOrder
|
||||
);
|
||||
|
||||
_this.indexOrder++;
|
||||
} else {
|
||||
_this.addPairSelects(
|
||||
'.retailcrm-customer-selects',
|
||||
'customer',
|
||||
_this.dataFields.customer,
|
||||
_this.indexCustomer
|
||||
);
|
||||
|
||||
_this.indexCustomer++;
|
||||
}
|
||||
});
|
||||
|
||||
jQuery(this.insertSelects).on('click', '.delete-select-retailcrm', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let objectJson = {}
|
||||
let entity = this.id.split('-')[0];
|
||||
let index = this.id.split('-')[1];
|
||||
|
||||
if (entity === 'order') {
|
||||
objectJson = JSON.parse(_this.orderTextArea.value);
|
||||
} else {
|
||||
objectJson = JSON.parse(_this.customerTextArea.value);
|
||||
}
|
||||
|
||||
let metaFiled = jQuery(`#metaFields-${entity}-${index}`);
|
||||
let valueMetaField = metaFiled.val();
|
||||
|
||||
for (let i = 1; i <= index; i++) {
|
||||
jQuery(`#select-pair-${entity}-${i}`).remove();
|
||||
}
|
||||
|
||||
delete objectJson[valueMetaField];
|
||||
|
||||
if (entity === 'order') {
|
||||
_this.indexOrder = _this.restoreSelectsData(
|
||||
objectJson,
|
||||
_this.dataFields.order,
|
||||
'order',
|
||||
1
|
||||
);
|
||||
|
||||
_this.orderTextArea.value = JSON.stringify(objectJson);
|
||||
} else {
|
||||
_this.indexCustomer = _this.restoreSelectsData(
|
||||
objectJson,
|
||||
_this.dataFields.customer,
|
||||
'customer',
|
||||
1
|
||||
);
|
||||
|
||||
_this.customerTextArea.value = JSON.stringify(objectJson);
|
||||
}
|
||||
|
||||
jQuery(`#add-new-select-${entity}-retailcrm`).removeClass('retailcrm-hidden')
|
||||
});
|
||||
}
|
||||
|
||||
RetailcrmMetaFields.prototype.restoreSelectsData = function (objectJson, dataEntity, entity, index) {
|
||||
for (let key in objectJson) {
|
||||
let getMetaSelect = `#metaFields-${entity}-${index} option[value=${key}]`;
|
||||
let getCustomSelect = `#customFields-${entity}-${index} option[value=${objectJson[key]}]`;
|
||||
|
||||
this.addPairSelects(`.retailcrm-${entity}-selects`, entity, dataEntity, index);
|
||||
|
||||
jQuery(getMetaSelect).prop('selected', true);
|
||||
jQuery(getCustomSelect).prop('selected', true);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
RetailcrmMetaFields.prototype.getSelectsData = function (textarea, entity, idChangeSelect, typeSelect) {
|
||||
let _this = this;
|
||||
let selectsData = {};
|
||||
|
||||
if (!jQuery.isEmptyObject(textarea)) {
|
||||
let objectJson = JSON.parse(textarea);
|
||||
let field = jQuery(`#${typeSelect}-${entity}-${idChangeSelect}`);
|
||||
let selectedField = jQuery(`#${typeSelect}-${entity}-${idChangeSelect} option:selected`);
|
||||
let fieldValue = field.val();
|
||||
|
||||
if (Object.values(objectJson).includes(fieldValue) || fieldValue in objectJson) {
|
||||
_this.addWarning(field, selectedField.text());
|
||||
|
||||
// Search key in object by id selects
|
||||
let counter = 0;
|
||||
|
||||
for (let key in objectJson) {
|
||||
counter++;
|
||||
|
||||
if (counter == idChangeSelect) {
|
||||
delete objectJson[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (entity === 'order') {
|
||||
_this.orderTextArea.value = JSON.stringify(objectJson);
|
||||
} else {
|
||||
_this.customerTextArea.value = JSON.stringify(objectJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 1; i <= jQuery(`.retailcrm-${entity}-selects div`).length; i++) {
|
||||
let metaFiled = jQuery(`#metaFields-${entity}-${i}`);
|
||||
let customField = jQuery(`#customFields-${entity}-${i}`);
|
||||
let valueMetaField = metaFiled.val();
|
||||
let valueCustomField = customField.val();
|
||||
|
||||
if (valueMetaField === 'default_retailcrm' || valueCustomField === 'default_retailcrm') {
|
||||
continue;
|
||||
}
|
||||
|
||||
_this.deleteWarning(metaFiled);
|
||||
_this.deleteWarning(customField);
|
||||
|
||||
selectsData[valueMetaField] = valueCustomField;
|
||||
}
|
||||
|
||||
return selectsData;
|
||||
}
|
||||
|
||||
RetailcrmMetaFields.prototype.createSelects = function (dataFields) {
|
||||
let orderData = dataFields.order;
|
||||
let customerData = dataFields.customer;
|
||||
|
||||
//Create selects for order data
|
||||
this.buildElements('.retailcrm-order-selects', 'order', this.indexOrder);
|
||||
this.fillSelects(orderData, 'order', this.indexOrder);
|
||||
|
||||
//Create selects for customer data
|
||||
this.buildElements('.retailcrm-customer-selects', 'customer', this.indexCustomer);
|
||||
this.fillSelects(customerData, 'customer', this.indexCustomer);
|
||||
|
||||
this.indexOrder++;
|
||||
this.indexCustomer++;
|
||||
}
|
||||
|
||||
RetailcrmMetaFields.prototype.buildElements = function (element, entity, index) {
|
||||
jQuery(element).append(
|
||||
`<div class="retailcrm-select-pair" id="select-pair-${entity}-${index}">` +
|
||||
`<select class="retailcrm-meta-select" id="metaFields-${entity}-${index}"></select>` +
|
||||
`<select class="retailcrm-meta-select" id="customFields-${entity}-${index}"></select>` +
|
||||
`<button class="delete-select-retailcrm" id="${entity}-${index}"></button></div>`
|
||||
);
|
||||
|
||||
jQuery(`#add-new-select-${entity}-retailcrm`).insertAfter(`#select-pair-${entity}-${index}`);
|
||||
}
|
||||
|
||||
RetailcrmMetaFields.prototype.fillSelects = function (data, entity, index) {
|
||||
let allCountCrmFields = Object.keys(data.custom).length + Object.keys(data.crmDefault).length;
|
||||
|
||||
if (Object.keys(data.meta).length - 1 <= index || allCountCrmFields - 1 <= index) {
|
||||
if (entity === 'order') {
|
||||
jQuery('#add-new-select-order-retailcrm').addClass('retailcrm-hidden')
|
||||
} else {
|
||||
jQuery('#add-new-select-customer-retailcrm').addClass('retailcrm-hidden')
|
||||
}
|
||||
}
|
||||
|
||||
// Set meta fields in select
|
||||
jQuery.each(data.meta, function(key, value) {
|
||||
jQuery(`#metaFields-${entity}-${index}`)
|
||||
.append(jQuery('<option></option>')
|
||||
.attr('value', key)
|
||||
.text(value));
|
||||
});
|
||||
|
||||
// Set custom fields in select
|
||||
jQuery.each(data.custom, function(key, value) {
|
||||
jQuery(`#customFields-${entity}-${index}`)
|
||||
.append(jQuery('<option></option>')
|
||||
.attr('value', key)
|
||||
.text(value));
|
||||
});
|
||||
|
||||
jQuery(`#customFields-${entity}-${index}`)
|
||||
.append(jQuery(`<optgroup id=default-${entity}-${index}-crm-fields label = '${data.tr_default_crm_fields}'></optgroup>`));
|
||||
|
||||
jQuery.each(data.crmDefault, function(key, value) {
|
||||
jQuery(`#default-${entity}-${index}-crm-fields`)
|
||||
.append(jQuery('<option></option>')
|
||||
.attr('value', key)
|
||||
.text(value));
|
||||
});
|
||||
}
|
||||
|
||||
RetailcrmMetaFields.prototype.addPairSelects = function (element, entity, data, index) {
|
||||
this.buildElements(element, entity, index);
|
||||
this.fillSelects(data, entity, index);
|
||||
}
|
||||
|
||||
RetailcrmMetaFields.prototype.addWarning = function (field, fieldValue) {
|
||||
alert(fieldValue + ' already been selected');
|
||||
|
||||
field.prop('value', 'default_retailcrm');
|
||||
field.addClass('red-selected-retailcrm');
|
||||
}
|
||||
|
||||
RetailcrmMetaFields.prototype.deleteWarning = function (field) {
|
||||
if (field.css('border-color') === "rgb(255, 0, 0)") {
|
||||
field.removeClass('red-selected-retailcrm')
|
||||
}
|
||||
}
|
||||
|
||||
window.RetailcrmMetaFields = RetailcrmMetaFields;
|
||||
|
||||
if (typeof RetailcrmMetaFields !== 'undefined') {
|
||||
new window.RetailcrmMetaFields();
|
||||
}
|
||||
});
|
17
src/assets/js/retailcrm-module-settings.js
Normal file
17
src/assets/js/retailcrm-module-settings.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
jQuery(function () {
|
||||
if (document.querySelector('#woocommerce_integration-retailcrm_bind_by_sku')) {
|
||||
document.querySelector('#woocommerce_integration-retailcrm_bind_by_sku').onchange = function() {
|
||||
let useXmlId = this.checked ? 'yes' : 'no';
|
||||
|
||||
document.querySelector('.submit').onmousedown = function() {
|
||||
jQuery.ajax({
|
||||
url: AdminUrl.url + '/admin-ajax.php?action=generate_icml',
|
||||
method: 'POST',
|
||||
timeout: 0,
|
||||
data: {useXmlId: useXmlId},
|
||||
dataType: 'json'
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
124
src/config/objects.xml
Normal file
124
src/config/objects.xml
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<options>
|
||||
<fields>
|
||||
<field id="id" group="customer">id</field>
|
||||
<field id="first_name" group="customer">firstName</field>
|
||||
<field id="last_name" group="customer">lastName</field>
|
||||
<field id="patronymic" group="customer">patronymic</field>
|
||||
<field id="email" group="customer">email</field>
|
||||
<field id="birthday" group="customer">birthday</field>
|
||||
<field id="phones" group="customer">phones</field>
|
||||
<field id="manager" group="customer">manager</field>
|
||||
<field id="commentary" group="customer">commentary</field>
|
||||
<field id="external_id" group="customer">externalId</field>
|
||||
<field id="cumulative_discount" group="customer">cumulativeDiscount</field>
|
||||
<field id="personal_discount" group="customer">personalDiscount</field>
|
||||
<field id="discount_card_number" group="customer">discountCardNumber</field>
|
||||
|
||||
<field id="id" group="customerCorporate">id</field>
|
||||
<field id="external_id" group="customerCorporate">externalId</field>
|
||||
<field id="nick_name" group="customerCorporate">nickName</field>
|
||||
<field id="vip" group="customerCorporate">vip</field>
|
||||
<field id="bad" group="customerCorporate">bad</field>
|
||||
<field id="custom_fields" group="customerCorporate">customFields</field>
|
||||
<field id="personal_discount" group="customerCorporate">personalDiscount</field>
|
||||
<field id="discount_card_number" group="customerCorporate">discountCardNumber</field>
|
||||
<field id="manager" group="customerCorporate">manager</field>
|
||||
<field id="address" group="customerCorporate">address</field>
|
||||
<field id="main_contact" group="customerCorporate">mainCustomerContact</field>
|
||||
<field id="company.contragent.i_n_n" group="customerCorporate">companyInn</field>
|
||||
<field id="main_company" group="customerCorporate">company</field>
|
||||
|
||||
<field id="address.index" group="customerAddress">index</field>
|
||||
<field id="address.country" group="customerAddress">country</field>
|
||||
<field id="address.region" group="customerAddress">region</field>
|
||||
<field id="address.city" group="customerAddress">city</field>
|
||||
<field id="address.street" group="customerAddress">street</field>
|
||||
<field id="address.building" group="customerAddress">building</field>
|
||||
<field id="address.house" group="customerAddress">house</field>
|
||||
<field id="address.block" group="customerAddress">block</field>
|
||||
<field id="address.flat" group="customerAddress">flat</field>
|
||||
<field id="address.floor" group="customerAddress">floor</field>
|
||||
<field id="address.intercom_code" group="customerAddress">intercomCode</field>
|
||||
<field id="address.metro" group="customerAddress">metro</field>
|
||||
<field id="address.notes" group="customerAddress">notes</field>
|
||||
|
||||
<field id="contragent.contragent_type" group="customerContragent">contragentType</field>
|
||||
<field id="contragent.legal_name" group="customerContragent">legalName</field>
|
||||
<field id="contragent.legal_address" group="customerContragent">legalAddress</field>
|
||||
<field id="contragent.certificate_number" group="customerContragent">certificateNumber</field>
|
||||
<field id="contragent.certificate_date" group="customerContragent">certificateDate</field>
|
||||
<field id="contragent.bank" group="customerContragent">bank</field>
|
||||
<field id="contragent.bank_address" group="customerContragent">bankAddress</field>
|
||||
<field id="contragent.corr_account" group="customerContragent">corrAccount</field>
|
||||
<field id="contragent.bank_account" group="customerContragent">bankAccount</field>
|
||||
|
||||
<field id="id" group="order">id</field>
|
||||
<field id="created_at" group="order">createdAt</field>
|
||||
<field id="order_type" group="order">orderType</field>
|
||||
<field id="order_method" group="order">orderMethod</field>
|
||||
<field id="site" group="order">site</field>
|
||||
<field id="status" group="order">status</field>
|
||||
<field id="customer" group="order">customer</field>
|
||||
<field id="manager" group="order">manager</field>
|
||||
<field id="first_name" group="order">firstName</field>
|
||||
<field id="last_name" group="order">lastName</field>
|
||||
<field id="patronymic" group="order">patronymic</field>
|
||||
<field id="phone" group="order">phone</field>
|
||||
<field id="additional_phone" group="order">additionalPhone</field>
|
||||
<field id="email" group="order">email</field>
|
||||
<field id="payment_type" group="order">paymentType</field>
|
||||
<field id="payment_status" group="order">paymentStatus</field>
|
||||
<field id="discount" group="order">discount</field>
|
||||
<field id="discount_percent" group="order">discountPercent</field>
|
||||
<field id="prepay_sum" group="order">prepaySum</field>
|
||||
<field id="customer_comment" group="order">customerComment</field>
|
||||
<field id="manager_comment" group="order">managerComment</field>
|
||||
<field id="shipment_store" group="order">shipmentStore</field>
|
||||
<field id="shipment_date" group="order">shipmentDate</field>
|
||||
<field id="shipped" group="order">shipped</field>
|
||||
<field id="contact" group="order">contact</field>
|
||||
<field id="company" group="order">company</field>
|
||||
|
||||
<field id="payment" group="order">payment</field>
|
||||
<field id="payments.amount" group="order">amount</field>
|
||||
<field id="payments.paid_at" group="order">paidAt</field>
|
||||
<field id="payments.comment" group="order">comment</field>
|
||||
<field id="payments.type" group="order">type</field>
|
||||
<field id="payments.status" group="order">status</field>
|
||||
|
||||
<field id="order_product.id" group="item">id</field>
|
||||
<field id="order_product.initial_price" group="item">initialPrice</field>
|
||||
<field id="order_product.discount_total" group="item">discountTotal</field>
|
||||
<field id="order_product.discount_percent" group="item">discountPercent</field>
|
||||
<field id="order_product.quantity" group="item">quantity</field>
|
||||
<field id="order_product.status" group="item">status</field>
|
||||
<field id="order_product.summ" group="item">summ</field>
|
||||
<field id="order_product.price_type" group="item">priceType</field>
|
||||
|
||||
<field id="delivery_type" group="delivery">code</field>
|
||||
<field id="delivery_service" group="delivery">service</field>
|
||||
<field id="delivery_date" group="delivery">date</field>
|
||||
<field id="delivery_time" group="delivery">time</field>
|
||||
<field id="delivery_cost" group="delivery">cost</field>
|
||||
<field id="delivery_net_cost" group="delivery">netCost</field>
|
||||
|
||||
<field id="delivery_address.country" group="orderAddress">country</field>
|
||||
<field id="delivery_address.index" group="orderAddress">index</field>
|
||||
<field id="delivery_address.region" group="orderAddress">region</field>
|
||||
<field id="delivery_address.city" group="orderAddress">city</field>
|
||||
<field id="delivery_address.street" group="orderAddress">street</field>
|
||||
<field id="delivery_address.building" group="orderAddress">building</field>
|
||||
<field id="delivery_address.house" group="orderAddress">house</field>
|
||||
<field id="delivery_address.block" group="orderAddress">block</field>
|
||||
<field id="delivery_address.flat" group="orderAddress">flat</field>
|
||||
<field id="delivery_address.floor" group="orderAddress">floor</field>
|
||||
<field id="delivery_address.intercom_code" group="orderAddress">intercomCode</field>
|
||||
<field id="delivery_address.metro" group="orderAddress">metro</field>
|
||||
<field id="delivery_address.notes" group="orderAddress">notes</field>
|
||||
|
||||
<field id="integration_delivery_data.status" group="integrationDelivery">status</field>
|
||||
<field id="integration_delivery_data.track_number" group="integrationDelivery">trackNumber</field>
|
||||
<field id="integration_delivery_data.courier" group="integrationDelivery">courier</field>
|
||||
</fields>
|
||||
</options>
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Abstract_Builder - Builds data for CRM.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
abstract class WC_Retailcrm_Abstract_Builder implements WC_Retailcrm_Builder_Interface
|
||||
{
|
||||
/** @var array|mixed $data */
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @param array|mixed $data
|
||||
*
|
||||
* @return \WC_Retailcrm_Abstract_Builder
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|mixed
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\WC_Retailcrm_Builder_Interface
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->data = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns key if it's present in data array (or object which implements ArrayAccess).
|
||||
* Returns default value if key is not present in data, or data is not accessible as array.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function dataValue($key, $default = '')
|
||||
{
|
||||
return self::arrayValue($this->data, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns key from array if it's present in array
|
||||
*
|
||||
* @param array|\ArrayObject $data
|
||||
* @param mixed $key
|
||||
* @param string $default
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
protected static function arrayValue($data, $key, $default = '')
|
||||
{
|
||||
if (!is_array($data) && !($data instanceof ArrayAccess)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $data) && !empty($data[$key])) {
|
||||
return $data[$key];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WC_Retailcrm_Builder_Interface
|
||||
*/
|
||||
abstract public function build();
|
||||
|
||||
/**
|
||||
* Returns builder result. Should return null if WC_Retailcrm_Abstract_Builder::isBuilt() == false.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
abstract public function getResult();
|
||||
}
|
145
src/include/abstracts/class-wc-retailcrm-abstracts-address.php
Normal file
145
src/include/abstracts/class-wc-retailcrm-abstracts-address.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Abstracts_Address - Builds data for addresses orders/customers.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
abstract class WC_Retailcrm_Abstracts_Address extends WC_Retailcrm_Abstracts_Data
|
||||
{
|
||||
/**
|
||||
* Divider for order delivery address_1 and address_2
|
||||
*/
|
||||
const ADDRESS_LINE_DIVIDER = ' || ';
|
||||
|
||||
/**
|
||||
* Returns shipping address from order.
|
||||
*
|
||||
* @param \WC_Order $order
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOrderAddress($order)
|
||||
{
|
||||
if (!$order instanceof WC_Order) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'index' => $order->get_shipping_postcode(),
|
||||
'city' => $order->get_shipping_city(),
|
||||
'region' => $this->getRegion($order->get_shipping_country(), $order->get_shipping_state()),
|
||||
'text' => $this->getText($order, 'order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns billing address from customer.
|
||||
*
|
||||
* @param \WC_Customer $customer
|
||||
* @param \WC_Order $order
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCustomerAddress($customer, $order)
|
||||
{
|
||||
if (!$customer instanceof WC_Customer) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$customerBillingAddress = $customer->get_billing_address();
|
||||
|
||||
if ($order instanceof WC_Order && empty($customerBillingAddress)) {
|
||||
return [
|
||||
'index' => $order->get_billing_postcode(),
|
||||
'countryIso' => $this->getCountryCode($order->get_billing_country()),
|
||||
'region' => $this->getRegion($order->get_billing_country(), $order->get_billing_state()),
|
||||
'city' => $order->get_billing_city(),
|
||||
'text' => $this->getText($order),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'index' => $customer->get_billing_postcode(),
|
||||
'countryIso' => $this->getCountryCode($customer->get_billing_country()),
|
||||
'region' => $this->getRegion($customer->get_billing_country(), $customer->get_billing_state()),
|
||||
'city' => $customer->get_billing_city(),
|
||||
'text' => $this->getText($customer),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Glue two addresses
|
||||
*
|
||||
* @param string $address1
|
||||
* @param string $address2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function joinAddresses(string $address1 = '', string $address2 = '')
|
||||
{
|
||||
return implode(self::ADDRESS_LINE_DIVIDER, array_filter([$address1, $address2]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate countryIso. Check if a given code represents a valid ISO 3166-1 alpha-2 code.
|
||||
*
|
||||
* @param $countryCode
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getCountryCode($countryCode)
|
||||
{
|
||||
$countries = new WC_Countries();
|
||||
|
||||
return $countries->country_exists($countryCode) ? $countryCode : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns state name by it's code
|
||||
*
|
||||
* @param string $countryCode
|
||||
* @param string $stateCode
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getRegion(string $countryCode, string $stateCode)
|
||||
{
|
||||
if (preg_match('/^[A-Z\-0-9]{0,5}$/', $stateCode) && !is_null($countryCode)) {
|
||||
$countriesProvider = new WC_Countries();
|
||||
$states = $countriesProvider->get_states($countryCode);
|
||||
|
||||
if (!empty($states) && array_key_exists($stateCode, $states)) {
|
||||
return (string) $states[$stateCode];
|
||||
}
|
||||
}
|
||||
|
||||
return $stateCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data for CRM field 'text'.
|
||||
* If type entity equals 'order', get address for order and use shipping address,
|
||||
* else get address for customer and use billing address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getText($wcEntity, $typeEntity = 'customer')
|
||||
{
|
||||
if ($typeEntity === 'order') {
|
||||
return empty($wcEntity->get_shipping_address_2())
|
||||
? $wcEntity->get_shipping_address_1()
|
||||
: $this->joinAddresses($wcEntity->get_shipping_address_1(), $wcEntity->get_shipping_address_2());
|
||||
} else {
|
||||
return empty($wcEntity->get_billing_address_2())
|
||||
? $wcEntity->get_billing_address_1()
|
||||
: $this->joinAddresses($wcEntity->get_billing_address_1(), $wcEntity->get_billing_address_2());
|
||||
}
|
||||
}
|
||||
}
|
59
src/include/abstracts/class-wc-retailcrm-abstracts-data.php
Normal file
59
src/include/abstracts/class-wc-retailcrm-abstracts-data.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Abstracts_Data - Class manage different data.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
abstract class WC_Retailcrm_Abstracts_Data
|
||||
{
|
||||
/** @var array */
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
abstract public function build($data);
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function setField($field, $value)
|
||||
{
|
||||
if (isset($this->data[$field]) && \gettype($value) !== \gettype($this->data[$field])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->data[$field] = $value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $fields
|
||||
*/
|
||||
protected function setDataFields($fields)
|
||||
{
|
||||
if (!empty($fields)) {
|
||||
foreach ($fields as $field => $value) {
|
||||
$this->setField($field, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
1033
src/include/abstracts/class-wc-retailcrm-abstracts-settings.php
Normal file
1033
src/include/abstracts/class-wc-retailcrm-abstracts-settings.php
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,16 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 5.3
|
||||
* PHP version 7.0
|
||||
*
|
||||
* WC_Retailcrm_Exception_Curl class
|
||||
* Class WC_Retailcrm_Exception_Curl.
|
||||
*
|
||||
* @category RetailCRM
|
||||
* @package WC_Retailcrm_Exception_Curl
|
||||
* @author RetailCRM <dev@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://retailcrm.ru/docs/Developers/ApiVersion4
|
||||
* @link http://retailcrm.ru/docs/Developers/ApiVersion5
|
||||
*/
|
||||
class WC_Retailcrm_Exception_Curl extends \RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 5.3
|
||||
* PHP version 7.0
|
||||
*
|
||||
* WC_Retailcrm_Exception_Json class
|
||||
* Class WC_Retailcrm_Exception_Json.
|
||||
*
|
||||
* @category RetailCRM
|
||||
* @package WC_Retailcrm_Exception_Json
|
||||
* @author RetailCRM <dev@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://retailcrm.ru/docs/Developers/ApiVersion5
|
||||
*/
|
||||
|
||||
class WC_Retailcrm_Exception_Json extends \DomainException
|
||||
{
|
||||
|
||||
}
|
||||
|
136
src/include/api/class-wc-retailcrm-proxy.php
Normal file
136
src/include/api/class-wc-retailcrm-proxy.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Proxy')) :
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Proxy - RetailCRM Integration.
|
||||
*
|
||||
* @category Integration
|
||||
* @package WC_Retailcrm_Proxy
|
||||
* @author RetailCRM <dev@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://retailcrm.ru/docs/Developers/ApiVersion5
|
||||
*/
|
||||
class WC_Retailcrm_Proxy
|
||||
{
|
||||
protected $retailcrm;
|
||||
protected $corporateEnabled;
|
||||
|
||||
public function __construct($api_url, $api_key, $corporateEnabled = false)
|
||||
{
|
||||
$this->corporateEnabled = $corporateEnabled;
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Client_V5')) {
|
||||
include_once(WC_Integration_Retailcrm::checkCustomFile('include/api/class-wc-retailcrm-client-v5.php'));
|
||||
}
|
||||
|
||||
$this->retailcrm = new WC_Retailcrm_Client_V5($api_url, $api_key);
|
||||
}
|
||||
|
||||
public function getCorporateEnabled(): ?bool
|
||||
{
|
||||
return $this->corporateEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response will be omitted in logs for those methods
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function methodsWithoutFullLog(): array
|
||||
{
|
||||
$methodsList = [
|
||||
'statusesList',
|
||||
'paymentTypesList',
|
||||
'deliveryTypesList',
|
||||
'orderMethodsList',
|
||||
'storesList',
|
||||
];
|
||||
|
||||
foreach ($methodsList as $key => $method) {
|
||||
$method = get_class($this->retailcrm) . '::' . $method;
|
||||
$methodsList[$key] = $method;
|
||||
}
|
||||
|
||||
return $methodsList;
|
||||
}
|
||||
|
||||
public function __call($method, $arguments)
|
||||
{
|
||||
$response = null;
|
||||
$called = sprintf('%s::%s', get_class($this->retailcrm), $method);
|
||||
|
||||
try {
|
||||
$response = $this->getResponse($method, $arguments);
|
||||
|
||||
if (is_string($response)) {
|
||||
WC_Retailcrm_Logger::info($method, $response, [], WC_Retailcrm_Logger::RESPONSE);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (!$response instanceof WC_Retailcrm_Response) {
|
||||
WC_Retailcrm_Logger::error(
|
||||
$method,
|
||||
sprintf("[%s] null (no response whatsoever)", $called),
|
||||
[],
|
||||
WC_Retailcrm_Logger::RESPONSE
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->logResponse($response, $method, $called);
|
||||
} catch (WC_Retailcrm_Exception_Curl|WC_Retailcrm_Exception_Json|InvalidArgumentException $exception) {
|
||||
WC_Retailcrm_Logger::exception($method, $exception);
|
||||
}
|
||||
|
||||
return $response instanceof WC_Retailcrm_Response ? $response : new WC_Retailcrm_Response(900, '{}');
|
||||
}
|
||||
|
||||
private function getResponse($method, $arguments)
|
||||
{
|
||||
WC_Retailcrm_Logger::info(
|
||||
$method,
|
||||
$arguments === [] ? '[no params]' : '[with params]',
|
||||
['params' => $arguments],
|
||||
WC_Retailcrm_Logger::REQUEST
|
||||
);
|
||||
|
||||
return call_user_func_array(array($this->retailcrm, $method), $arguments);
|
||||
}
|
||||
|
||||
private function logResponse(WC_Retailcrm_Response $response, $method, $called): void
|
||||
{
|
||||
if ($response->isSuccessful()) {
|
||||
if (in_array($called, $this->methodsWithoutFullLog())) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
$method,
|
||||
'Ok',
|
||||
['body' => 'request was successful, but response is omitted'],
|
||||
WC_Retailcrm_Logger::RESPONSE
|
||||
);
|
||||
} else {
|
||||
WC_Retailcrm_Logger::info(
|
||||
$method,
|
||||
'Ok',
|
||||
['body' => json_decode($response->getRawResponse(), true)],
|
||||
WC_Retailcrm_Logger::RESPONSE
|
||||
);
|
||||
}
|
||||
} else {
|
||||
WC_Retailcrm_Logger::error(
|
||||
$method,
|
||||
sprintf(
|
||||
"Error: [HTTP-code %s] %s",
|
||||
$response->getStatusCode(),
|
||||
$response->getErrorString()
|
||||
),
|
||||
['response' => json_decode($response->getRawResponse(), true)],
|
||||
WC_Retailcrm_Logger::RESPONSE
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
endif;
|
|
@ -1,24 +1,24 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Exception_Curl')) {
|
||||
include_once(WC_Integration_Retailcrm::checkCustomFile('include/api/class-wc-retailcrm-exception-curl.php'));
|
||||
}
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Response')) {
|
||||
include_once(WC_Integration_Retailcrm::checkCustomFile('include/api/class-wc-retailcrm-response.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP version 5.3
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Request class
|
||||
* Class WC_Retailcrm_Request - Request class.
|
||||
*
|
||||
* @category Integration
|
||||
* @package WC_Retailcrm_Request
|
||||
* @author RetailCRM <dev@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://retailcrm.ru/docs/Developers/ApiVersion5
|
||||
*/
|
||||
|
||||
if ( ! class_exists( 'WC_Retailcrm_Exception_Curl' ) ) {
|
||||
include_once( __DIR__ . '/class-wc-retailcrm-exception-curl.php' );
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Retailcrm_Response' ) ) {
|
||||
include_once( __DIR__ . '/class-wc-retailcrm-response.php' );
|
||||
}
|
||||
|
||||
class WC_Retailcrm_Request
|
||||
{
|
||||
const METHOD_GET = 'GET';
|
||||
|
@ -33,16 +33,9 @@ class WC_Retailcrm_Request
|
|||
* @param string $url api url
|
||||
* @param array $defaultParameters array of parameters
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($url, array $defaultParameters = array())
|
||||
public function __construct($url, array $defaultParameters = [])
|
||||
{
|
||||
if (false === stripos($url, 'https://')) {
|
||||
throw new \InvalidArgumentException(
|
||||
'API schema requires HTTPS protocol'
|
||||
);
|
||||
}
|
||||
|
||||
$this->url = $url;
|
||||
$this->defaultParameters = $defaultParameters;
|
||||
}
|
||||
|
@ -64,9 +57,9 @@ class WC_Retailcrm_Request
|
|||
public function makeRequest(
|
||||
$path,
|
||||
$method,
|
||||
array $parameters = array()
|
||||
array $parameters = []
|
||||
) {
|
||||
$allowedMethods = array(self::METHOD_GET, self::METHOD_POST);
|
||||
$allowedMethods = [self::METHOD_GET, self::METHOD_POST];
|
||||
|
||||
if (!in_array($method, $allowedMethods, false)) {
|
||||
throw new \InvalidArgumentException(
|
||||
|
@ -78,7 +71,16 @@ class WC_Retailcrm_Request
|
|||
);
|
||||
}
|
||||
|
||||
$parameters = array_merge($this->defaultParameters, $parameters);
|
||||
$parameters = self::METHOD_GET === $method
|
||||
? array_merge($this->defaultParameters, $parameters, [
|
||||
'cms_source' => 'WordPress',
|
||||
'cms_version' => function_exists('get_bloginfo') ? get_bloginfo('version') : '',
|
||||
'woo_version' => WC()->version ?? '',
|
||||
'php_version' => function_exists('phpversion') ? phpversion() : '',
|
||||
'module_version' => WC_Integration_Retailcrm::MODULE_VERSION,
|
||||
'ga_option_is_active' => getOptionByCode('ua') === WC_Retailcrm_Abstracts_Settings::YES,
|
||||
])
|
||||
: array_merge($this->defaultParameters, $parameters);
|
||||
|
||||
$url = $this->url . $path;
|
||||
|
|
@ -1,21 +1,20 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Exception_Json')) {
|
||||
include_once(WC_Integration_Retailcrm::checkCustomFile('include/api/class-wc-retailcrm-exception-json.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP version 5.3
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Response class
|
||||
* Class WC_Retailcrm_Response - Response class.
|
||||
*
|
||||
* @category Integration
|
||||
* @package WC_Retailcrm_Response
|
||||
* @author RetailCRM <dev@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://retailcrm.ru/docs/Developers/ApiVersion5
|
||||
*/
|
||||
|
||||
if ( ! class_exists( 'WC_Retailcrm_Exception_Json' ) ) {
|
||||
include_once( __DIR__ . '/class-wc-retailcrm-exception-json.php' );
|
||||
}
|
||||
|
||||
|
||||
class WC_Retailcrm_Response implements \ArrayAccess
|
||||
{
|
||||
// HTTP response status code
|
||||
|
@ -24,6 +23,9 @@ class WC_Retailcrm_Response implements \ArrayAccess
|
|||
// response assoc array
|
||||
protected $response;
|
||||
|
||||
// response raw data
|
||||
protected $rawResponse;
|
||||
|
||||
/**
|
||||
* ApiResponse constructor.
|
||||
*
|
||||
|
@ -35,6 +37,7 @@ class WC_Retailcrm_Response implements \ArrayAccess
|
|||
public function __construct($statusCode, $responseBody = null)
|
||||
{
|
||||
$this->statusCode = (int) $statusCode;
|
||||
$this->rawResponse = $responseBody;
|
||||
|
||||
if (!empty($responseBody)) {
|
||||
$response = json_decode($responseBody, true);
|
||||
|
@ -117,9 +120,8 @@ class WC_Retailcrm_Response implements \ArrayAccess
|
|||
* @param mixed $value value
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
throw new \BadMethodCallException('This activity not allowed');
|
||||
}
|
||||
|
@ -130,9 +132,8 @@ class WC_Retailcrm_Response implements \ArrayAccess
|
|||
* @param mixed $offset offset
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
throw new \BadMethodCallException('This call not allowed');
|
||||
}
|
||||
|
@ -142,9 +143,8 @@ class WC_Retailcrm_Response implements \ArrayAccess
|
|||
*
|
||||
* @param mixed $offset offset
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->response[$offset]);
|
||||
}
|
||||
|
@ -153,10 +153,10 @@ class WC_Retailcrm_Response implements \ArrayAccess
|
|||
* Get offset
|
||||
*
|
||||
* @param mixed $offset offset
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return mixed
|
||||
* TODO PHP < 8.0 не поддерживает тип mixed. Оператор | для перечисления типов также не поддерживается.
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
|
@ -166,4 +166,40 @@ class WC_Retailcrm_Response implements \ArrayAccess
|
|||
|
||||
return $this->response[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error string. If there's multiple errors present - they will be squashed into single string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getErrorString()
|
||||
{
|
||||
if ($this->offsetExists('errorMsg')) {
|
||||
return (string) $this->response['errorMsg'];
|
||||
}
|
||||
|
||||
if (is_array($this->response['errors']) && $this->offsetExists('errors')) {
|
||||
$errorMessage = '';
|
||||
|
||||
foreach ($this->response['errors'] as $error) {
|
||||
$errorMessage .= $error . ' >';
|
||||
}
|
||||
|
||||
if (strlen($errorMessage) > 2) {
|
||||
return (string) substr($errorMessage, 0, strlen($errorMessage) - 2);
|
||||
}
|
||||
|
||||
return $errorMessage;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getRawResponse()
|
||||
{
|
||||
return $this->rawResponse;
|
||||
}
|
||||
}
|
1387
src/include/class-wc-retailcrm-base.php
Normal file
1387
src/include/class-wc-retailcrm-base.php
Normal file
File diff suppressed because it is too large
Load diff
106
src/include/class-wc-retailcrm-cart.php
Normal file
106
src/include/class-wc-retailcrm-cart.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Carts')) :
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Cart - Allows transfer data carts with CMS.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Cart
|
||||
{
|
||||
protected $apiClient;
|
||||
protected $dateFormat;
|
||||
|
||||
protected $settings;
|
||||
|
||||
public function __construct($apiClient, $settings)
|
||||
{
|
||||
$this->apiClient = $apiClient;
|
||||
$this->settings = $settings;
|
||||
$this->dateFormat = 'Y-m-d H:i:sP';
|
||||
}
|
||||
|
||||
public function isCartExist($customerId, $site): bool
|
||||
{
|
||||
$getCart = $this->apiClient->cartGet($customerId, $site);
|
||||
|
||||
return !empty($getCart['cart']['externalId']);
|
||||
}
|
||||
|
||||
public function processCart($customerId, $cartItems, $site, $isCartExist): bool
|
||||
{
|
||||
$isSuccessful = false;
|
||||
|
||||
try {
|
||||
$crmCart = [
|
||||
'customer' => ['externalId' => $customerId],
|
||||
'clearAt' => null,
|
||||
'updatedAt' => date($this->dateFormat),
|
||||
'droppedAt' => date($this->dateFormat),
|
||||
'link' => wc_get_cart_url(),
|
||||
];
|
||||
|
||||
// If new cart, need set createdAt and externalId
|
||||
if (!$isCartExist) {
|
||||
$crmCart['createdAt'] = date($this->dateFormat);
|
||||
$crmCart['externalId'] = $customerId . uniqid('_', true);
|
||||
}
|
||||
|
||||
// If you delete one by one
|
||||
if (empty($cartItems)) {
|
||||
return $this->clearCart($customerId, $site, $isCartExist);
|
||||
}
|
||||
|
||||
$useXmlId = isset($this->settings['bind_by_sku']) && $this->settings['bind_by_sku'] === WC_Retailcrm_Base::YES;
|
||||
|
||||
foreach ($cartItems as $item) {
|
||||
$product = $item['data'];
|
||||
|
||||
$crmCart['items'][] = [
|
||||
'offer' => $useXmlId ? ['xmlId' => $product->get_sku()] : ['externalId' => $product->get_id()],
|
||||
'quantity' => $item['quantity'],
|
||||
'createdAt' => $product->get_date_created()->date($this->dateFormat) ?? date($this->dateFormat),
|
||||
'updatedAt' => $product->get_date_modified()->date($this->dateFormat) ?? date($this->dateFormat),
|
||||
'price' => wc_get_price_including_tax($product),
|
||||
];
|
||||
}
|
||||
|
||||
$crmCart = apply_filters(
|
||||
'retailcrm_process_cart',
|
||||
WC_Retailcrm_Plugin::clearArray($crmCart),
|
||||
$cartItems
|
||||
);
|
||||
|
||||
$setResponse = $this->apiClient->cartSet($crmCart, $site);
|
||||
$isSuccessful = $setResponse->isSuccessful() && !empty($setResponse['success']);
|
||||
} catch (Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(__METHOD__, $exception);
|
||||
}
|
||||
|
||||
return $isSuccessful;
|
||||
}
|
||||
|
||||
public function clearCart($customerId, $site, $isCartExist): bool
|
||||
{
|
||||
$isSuccessful = false;
|
||||
|
||||
try {
|
||||
if ($isCartExist) {
|
||||
$crmCart = ['customer' => ['externalId' => $customerId], 'clearedAt' => date($this->dateFormat)];
|
||||
$clearResponse = $this->apiClient->cartClear($crmCart, $site);
|
||||
$isSuccessful = $clearResponse->isSuccessful() && !empty($clearResponse['success']);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(__METHOD__, $exception);
|
||||
}
|
||||
|
||||
return $isSuccessful;
|
||||
}
|
||||
}
|
||||
endif;
|
759
src/include/class-wc-retailcrm-customers.php
Normal file
759
src/include/class-wc-retailcrm-customers.php
Normal file
|
@ -0,0 +1,759 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Customers')) :
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Customers - Allows transfer data customers with CMS.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Customers
|
||||
{
|
||||
/** @var bool | WC_Retailcrm_Proxy | \WC_Retailcrm_Client_V5 */
|
||||
protected $retailcrm;
|
||||
|
||||
/** @var array */
|
||||
protected $retailcrm_settings = [];
|
||||
|
||||
/** @var WC_Retailcrm_Customer_Address */
|
||||
protected $customer_address;
|
||||
|
||||
/** @var array */
|
||||
private $customer = [];
|
||||
|
||||
/** @var array */
|
||||
private $customerCorporate = [];
|
||||
|
||||
/** @var array */
|
||||
private $customerCorporateCompany = [];
|
||||
|
||||
/** @var array */
|
||||
private $customerCorporateAddress = [];
|
||||
|
||||
/**@var array */
|
||||
private $customFields = [];
|
||||
|
||||
/**@var null */
|
||||
public $isSubscribed = null;
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Customers constructor.
|
||||
*
|
||||
* @param bool | WC_Retailcrm_Proxy $retailcrm
|
||||
* @param array $retailcrm_settings
|
||||
* @param WC_Retailcrm_Customer_Address $customer_address
|
||||
*/
|
||||
public function __construct($retailcrm, $retailcrm_settings, $customer_address)
|
||||
{
|
||||
$this->retailcrm = $retailcrm;
|
||||
$this->retailcrm_settings = $retailcrm_settings;
|
||||
$this->customer_address = $customer_address;
|
||||
|
||||
if (!empty($retailcrm_settings['customer-meta-data-retailcrm'])) {
|
||||
$this->customFields = json_decode($retailcrm_settings['customer-meta-data-retailcrm'], true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return corporate customer
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCorporateCustomer()
|
||||
{
|
||||
return $this->customerCorporate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is corporate customers enabled in provided API
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCorporateEnabled()
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->retailcrm->getCorporateEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer can registration on site, we need:
|
||||
* 1. Check by email if the customer already exists in CRM - then update the customer details.
|
||||
* 2. If the customer is not in CRM, then create a new customer.
|
||||
*
|
||||
* @param int $customerId
|
||||
*
|
||||
* @return void|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public function registerCustomer($customerId)
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WC_Retailcrm_Logger::info(__METHOD__, 'Register WC_Customer ID: ' . $customerId);
|
||||
|
||||
$wcCustomer = new WC_Customer($customerId);
|
||||
$email = $wcCustomer->get_billing_email();
|
||||
|
||||
if (empty($email)) {
|
||||
$email = $wcCustomer->get_email();
|
||||
}
|
||||
|
||||
if (empty($email)) {
|
||||
WC_Retailcrm_Logger::error(
|
||||
__METHOD__,
|
||||
'Error: Customer email is empty, externalId: ' . $wcCustomer->get_id()
|
||||
);
|
||||
|
||||
return null;
|
||||
} else {
|
||||
$wcCustomer->set_billing_email($email);
|
||||
$wcCustomer->save();
|
||||
}
|
||||
|
||||
$response = $this->retailcrm->customersList(['email' => $email]);
|
||||
|
||||
if ($response->isSuccessful() && !empty($response['customers'])) {
|
||||
$customers = $response['customers'];
|
||||
$customer = reset($customers);
|
||||
|
||||
if (isset($customer['id'])) {
|
||||
$this->updateCustomerById($customerId, $customer['id']);
|
||||
|
||||
$builder = new WC_Retailcrm_WC_Customer_Builder();
|
||||
$builder
|
||||
->setWcCustomer($wcCustomer)
|
||||
->setPhones(!empty($customer['phones']) ? $customer['phones'] : [])
|
||||
->setAddress(!empty($customer['address']) ? $customer['address'] : false)
|
||||
->build()
|
||||
->getResult()
|
||||
->save();
|
||||
|
||||
WC_Retailcrm_Logger::info(__METHOD__, 'Customer was edited, externalId: ' . $wcCustomer->get_id());
|
||||
}
|
||||
} else {
|
||||
$this->createCustomer($customerId);
|
||||
|
||||
$message = $this->isSubscribed
|
||||
? 'The client has agreed to receive promotional newsletter, email: '
|
||||
: 'The client refused to receive promotional newsletters, email: ';
|
||||
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
sprintf('Customer was created, externalId: %s. %s', $wcCustomer->get_id(), $message . $email)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create customer in CRM
|
||||
*
|
||||
* @param int | WC_Customer $customer
|
||||
*
|
||||
* @param \WC_Order|null $order
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createCustomer($customer, $order = null)
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_int($customer)) {
|
||||
$customer = $this->wcCustomerGet($customer);
|
||||
}
|
||||
|
||||
if (!$customer instanceof WC_Customer) {
|
||||
WC_Retailcrm_Logger::error(__METHOD__, 'Customer not found');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->isCustomer($customer)) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Process WC_Customer ' . $customer->get_id(),
|
||||
['wc_customer' => WC_Retailcrm_Logger::formatWcObject($customer)]
|
||||
);
|
||||
$this->processCustomer($customer, $order);
|
||||
$response = $this->retailcrm->customersCreate($this->customer);
|
||||
|
||||
if ((!empty($response) && $response->isSuccessful()) && isset($response['id'])) {
|
||||
return $response['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update customer in CRM
|
||||
*
|
||||
* @param $customerId
|
||||
*
|
||||
* @return void|\WC_Customer
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateCustomer($customerId)
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
$customer = $this->wcCustomerGet($customerId);
|
||||
|
||||
if ($this->isCustomer($customer)) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Update WC_Customer ' . $customer->get_id(),
|
||||
['wc_customer' => WC_Retailcrm_Logger::formatWcObject($customer)]
|
||||
);
|
||||
$this->processCustomer($customer);
|
||||
$this->retailcrm->customersEdit($this->customer);
|
||||
}
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update customer in CRM by ID
|
||||
*
|
||||
* @param int $customerId
|
||||
* @param int|string $crmCustomerId
|
||||
*
|
||||
* @return void|\WC_Customer
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateCustomerById($customerId, $crmCustomerId)
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
$customer = $this->wcCustomerGet($customerId);
|
||||
|
||||
if ($this->isCustomer($customer)) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Update WC_Customer by CRM_Customer ID: ' . $crmCustomerId,
|
||||
['wc_customer' => WC_Retailcrm_Logger::formatWcObject($customer)]
|
||||
);
|
||||
$this->processCustomer($customer);
|
||||
$this->customer['id'] = $crmCustomerId;
|
||||
$this->retailcrm->customersEdit($this->customer, 'id');
|
||||
}
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create corporate customer in CRM
|
||||
*
|
||||
* @param int $crmCustomerId
|
||||
* @param int | WC_Customer $customer
|
||||
* @param \WC_Order $order
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createCorporateCustomerForOrder($crmCustomerId, $customer, $order)
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_int($customer)) {
|
||||
$customer = $this->wcCustomerGet($customer);
|
||||
}
|
||||
|
||||
if (!$customer instanceof WC_Customer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->isCustomer($customer)) {
|
||||
$this->processCorporateCustomer($crmCustomerId, $customer, $order);
|
||||
$response = $this->retailcrm->customersCorporateCreate($this->customerCorporate);
|
||||
|
||||
return $this->fillCorporateCustomer($response);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new address in corporate customer (if needed).
|
||||
*
|
||||
* @param int $corporateId
|
||||
* @param \WC_Customer $customer
|
||||
* @param \WC_Order|null $order
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function fillCorporateAddress($corporateId, $customer, $order = null)
|
||||
{
|
||||
$found = false;
|
||||
$builder = new WC_Retailcrm_Customer_Corporate_Address();
|
||||
$newAddress = $builder
|
||||
->setIsMain(false)
|
||||
->build($customer, $order)
|
||||
->getData();
|
||||
|
||||
$addresses = $this->retailcrm->customersCorporateAddresses(
|
||||
$corporateId,
|
||||
[],
|
||||
null,
|
||||
100,
|
||||
'id'
|
||||
);
|
||||
|
||||
if (!empty($addresses['addresses']) && $addresses->isSuccessful()) {
|
||||
foreach ($addresses['addresses'] as $address) {
|
||||
foreach ($newAddress as $field => $value) {
|
||||
if (isset($address[$field]) && $address[$field] != $value) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$found = true;
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$this->retailcrm->customersCorporateAddressesCreate(
|
||||
$corporateId,
|
||||
$newAddress,
|
||||
'id',
|
||||
$this->retailcrm->getSingleSiteForKey()
|
||||
);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills corporate customer with required data after customer was created or updated.
|
||||
* Create or update response after sending customer must be passed.
|
||||
*
|
||||
* @param \WC_Retailcrm_Response $response
|
||||
*
|
||||
* @return string|int|null
|
||||
*/
|
||||
protected function fillCorporateCustomer($response)
|
||||
{
|
||||
if ((empty($response) || !$response->isSuccessful()) && !$response->offsetExists('id')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$customerId = $response['id'];
|
||||
$response = $this->retailcrm->customersCorporateAddressesCreate(
|
||||
$customerId,
|
||||
$this->customerCorporateAddress,
|
||||
'id',
|
||||
$this->retailcrm->getSingleSiteForKey()
|
||||
);
|
||||
|
||||
if ($response->isSuccessful() && $response->offsetExists('id')) {
|
||||
$this->customerCorporateCompany['address'] = [
|
||||
'id' => $response['id'],
|
||||
];
|
||||
|
||||
$this->retailcrm->customersCorporateCompaniesCreate(
|
||||
$customerId,
|
||||
$this->customerCorporateCompany,
|
||||
'id',
|
||||
$this->retailcrm->getSingleSiteForKey()
|
||||
);
|
||||
}
|
||||
|
||||
return $customerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process customer for upload
|
||||
*
|
||||
* @param WC_Customer $customer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processCustomerForUpload($customer)
|
||||
{
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Processing for upload WC_Customer ' . $customer->get_id(),
|
||||
['wc_customer' => WC_Retailcrm_Logger::formatWcObject($customer)]
|
||||
);
|
||||
$this->processCustomer($customer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process customer
|
||||
*
|
||||
* @param WC_Customer $customer
|
||||
* @param WC_Order|null $order
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processCustomer($customer, $order = null)
|
||||
{
|
||||
$createdAt = $customer->get_date_created();
|
||||
$firstName = $customer->get_first_name();
|
||||
$lastName = $customer->get_last_name();
|
||||
$billingPhone = $customer->get_billing_phone();
|
||||
$email = strtolower($customer->get_billing_email());
|
||||
|
||||
if (empty($firstName) && empty($lastName) && $order instanceof WC_Order) {
|
||||
$firstName = $order->get_billing_first_name();
|
||||
$lastName = $order->get_billing_last_name();
|
||||
|
||||
if (empty($email)) {
|
||||
$email = $order->get_billing_email();
|
||||
}
|
||||
|
||||
if (empty($billingPhone)) {
|
||||
$billingPhone = $order->get_billing_phone();
|
||||
}
|
||||
}
|
||||
|
||||
// If a customer has placed an order as a guest, then $customer->get_date_created() == null,
|
||||
// then we take $order->get_date_created() order
|
||||
$createdAt = empty($createdAt) ? $order->get_date_created() : $createdAt;
|
||||
|
||||
$customerData = [
|
||||
'createdAt' => $createdAt->date('Y-m-d H:i:s'),
|
||||
'firstName' => !empty($firstName) ? $firstName : $customer->get_username(),
|
||||
'lastName' => $lastName,
|
||||
'email' => $email,
|
||||
'address' => $this->customer_address->build($customer, $order)->getData()
|
||||
];
|
||||
|
||||
if ($customer->get_id() > 0) {
|
||||
$customerData['externalId'] = $customer->get_id();
|
||||
}
|
||||
|
||||
// The guest client is unsubscribed by default
|
||||
if ($customer->get_id() === 0 && $customer->get_date_created() === null) {
|
||||
$customerData['subscribed'] = false;
|
||||
}
|
||||
|
||||
if ($this->isSubscribed !== null) {
|
||||
$customerData['subscribed'] = $this->isSubscribed;
|
||||
}
|
||||
|
||||
if (!empty($billingPhone)) {
|
||||
$customerData['phones'][] = [
|
||||
'number' => $billingPhone
|
||||
];
|
||||
}
|
||||
|
||||
// If the client is corporate, set the value isContact.
|
||||
if ($this->isCorporateEnabled()) {
|
||||
if ($order !== null) {
|
||||
$company = $order->get_billing_company();
|
||||
}
|
||||
|
||||
if (empty($company)) {
|
||||
$company = $customer->get_billing_company();
|
||||
}
|
||||
|
||||
if (!empty($company)) {
|
||||
$customerData['isContact'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->customFields)) {
|
||||
foreach ($this->customFields as $metaKey => $customKey) {
|
||||
$metaValue = $customer->get_meta($metaKey);
|
||||
|
||||
if (empty($metaValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($customKey, 'default-crm-field') !== false) {
|
||||
$crmField = explode('#', $customKey);
|
||||
|
||||
if (count($crmField) === 2 && isset($crmField[1])) {
|
||||
if ($crmField[1] === 'phones') {
|
||||
$customerData[$crmField[1]][] = ['number' => $metaValue];
|
||||
} elseif ($crmField[1] === 'tags') {
|
||||
$customerData['addTags'][] = $metaValue;
|
||||
} else {
|
||||
$customerData[$crmField[1]] = $metaValue;
|
||||
}
|
||||
} elseif (isset($crmField[1], $crmField[2])) {
|
||||
// For customer delivery
|
||||
$customerData[$crmField[1]][$crmField[2]] = $metaValue;
|
||||
}
|
||||
} else {
|
||||
$customerData['customFields'][$customKey] = $metaValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->customer = apply_filters(
|
||||
'retailcrm_process_customer',
|
||||
WC_Retailcrm_Plugin::clearArray($customerData),
|
||||
$customer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process corporate customer
|
||||
*
|
||||
* @param int $crmCustomerId
|
||||
* @param WC_Customer $customer
|
||||
* @param \WC_Order $order
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processCorporateCustomer($crmCustomerId, $customer, $order)
|
||||
{
|
||||
$data_company = [
|
||||
'isMain' => true,
|
||||
'name' => $order->get_billing_company()
|
||||
];
|
||||
|
||||
$data_customer = [
|
||||
'nickName' => $order->get_billing_company(),
|
||||
'customerContacts' => [
|
||||
[
|
||||
'isMain' => true,
|
||||
'customer' => [
|
||||
'id' => $crmCustomerId
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$corpAddress = new WC_Retailcrm_Customer_Corporate_Address();
|
||||
|
||||
$billingAddress = $corpAddress
|
||||
->setIsMain(true)
|
||||
->build($customer, $order)
|
||||
->getData();
|
||||
|
||||
if (!empty($billingAddress)) {
|
||||
$data_company['contragent']['legalAddress'] = implode(
|
||||
', ',
|
||||
[
|
||||
$billingAddress['index'],
|
||||
$billingAddress['city'],
|
||||
$billingAddress['region'],
|
||||
$billingAddress['text']
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->customerCorporateAddress = $billingAddress;
|
||||
|
||||
$this->customerCorporate = apply_filters(
|
||||
'retailcrm_process_customer_corporate',
|
||||
WC_Retailcrm_Plugin::clearArray($data_customer),
|
||||
$customer
|
||||
);
|
||||
|
||||
$this->customerCorporateCompany = apply_filters(
|
||||
'retailcrm_process_customer_corporate_company',
|
||||
WC_Retailcrm_Plugin::clearArray($data_company),
|
||||
$customer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $filter Search customer by fields.
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
private function searchCustomer($filter)
|
||||
{
|
||||
if (isset($filter['externalId'])) {
|
||||
$search = $this->retailcrm->customersGet($filter['externalId']);
|
||||
} elseif (isset($filter['email'])) {
|
||||
if (empty($filter['email'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If customer not corporate, we need unset this field.
|
||||
if (empty($filter['isContact'])) {
|
||||
unset($filter['isContact']);
|
||||
}
|
||||
|
||||
$search = $this->retailcrm->customersList($filter);
|
||||
}
|
||||
|
||||
if (!empty($search) && $search->isSuccessful()) {
|
||||
$customer = false;
|
||||
|
||||
if (isset($search['customers'])) {
|
||||
if (empty($search['customers'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($filter['email'])) {
|
||||
foreach ($search['customers'] as $finding) {
|
||||
if (isset($finding['email']) && $finding['email'] == $filter['email']) {
|
||||
$customer = $finding;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$dataCustomers = $search['customers'];
|
||||
$customer = reset($dataCustomers);
|
||||
}
|
||||
} else {
|
||||
$customer = !empty($search['customer']) ? $search['customer'] : false;
|
||||
}
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns customer data by externalId or by email, returns false in case of failure
|
||||
*
|
||||
* @param string $customerExternalId Customer externalId.
|
||||
* @param string $customerEmailOrPhone Customer email or phone.
|
||||
* @param bool $isContact Customer is the contact person.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public function findCustomerEmailOrId($customerExternalId, $customerEmailOrPhone, $isContact)
|
||||
{
|
||||
$customer = false;
|
||||
|
||||
if (!empty($customerExternalId)) {
|
||||
$customer = $this->searchCustomer(['externalId' => $customerExternalId]);
|
||||
}
|
||||
|
||||
if (!$customer && !empty($customerEmailOrPhone)) {
|
||||
$customer = $this->searchCustomer(['email' => $customerEmailOrPhone, 'isContact' => $isContact]);
|
||||
}
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search by provided filter, returns first found customer
|
||||
*
|
||||
* @param array $filter
|
||||
* @param bool $returnGroup Return all customers for group filter instead of first
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public function searchCorporateCustomer($filter, $returnGroup = false)
|
||||
{
|
||||
$search = $this->retailcrm->customersCorporateList($filter);
|
||||
|
||||
if (!empty($search) && $search->isSuccessful()) {
|
||||
if (isset($search['customersCorporate'])) {
|
||||
if (empty($search['customersCorporate'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($returnGroup) {
|
||||
return $search['customersCorporate'];
|
||||
} else {
|
||||
$dataCorporateCustomers = $search['customersCorporate'];
|
||||
$customer = reset($dataCorporateCustomers);
|
||||
}
|
||||
} else {
|
||||
$customer = false;
|
||||
}
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return WC_Customer
|
||||
* @throws Exception
|
||||
*/
|
||||
public function buildCustomerFromOrderData($order)
|
||||
{
|
||||
$new_customer = new WC_Customer();
|
||||
|
||||
foreach ($order->get_address('billing') as $prop => $value) {
|
||||
if (method_exists($new_customer, 'set_billing_' . $prop)) {
|
||||
$new_customer->{'set_billing_' . $prop}($value);
|
||||
}
|
||||
}
|
||||
|
||||
$new_customer->set_first_name($order->get_billing_first_name());
|
||||
$new_customer->set_last_name($order->get_billing_last_name());
|
||||
$new_customer->set_email($order->get_billing_email());
|
||||
$new_customer->set_date_created($order->get_date_created());
|
||||
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Build new customer from order data',
|
||||
['wc_customer' => WC_Retailcrm_Logger::formatWcObject($new_customer)]
|
||||
);
|
||||
|
||||
return $new_customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $customer_id
|
||||
*
|
||||
* @return WC_Customer
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function wcCustomerGet($customer_id)
|
||||
{
|
||||
return new WC_Customer($customer_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCustomer()
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if provided WP_User or WC_Customer should be uploaded to CRM
|
||||
*
|
||||
* @param \WC_Customer|\WP_User $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCustomer($user)
|
||||
{
|
||||
$clientRoles = wp_roles()->get_names();
|
||||
$clientRoles = apply_filters('retailcrm_customer_roles', WC_Retailcrm_Plugin::clearArray($clientRoles));
|
||||
|
||||
if ($user instanceof WP_User) {
|
||||
$userRole = !empty($user->roles[0]) ? $user->roles[0] : null;
|
||||
} elseif ($user instanceof WC_Customer) {
|
||||
$role = $user->get_role();
|
||||
$userRole = !empty($role) ? $role : null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_key_exists($userRole, $clientRoles);
|
||||
}
|
||||
}
|
||||
endif;
|
117
src/include/class-wc-retailcrm-daemon-collector.php
Normal file
117
src/include/class-wc-retailcrm-daemon-collector.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Daemon_Collector - Integration with Daemon Collector.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Daemon_Collector {
|
||||
/** @var self $instance */
|
||||
private static $instance;
|
||||
|
||||
/** @var array $options */
|
||||
private $options;
|
||||
|
||||
/** @var string $code */
|
||||
private $code = '';
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return WC_Retailcrm_Daemon_Collector
|
||||
*/
|
||||
public static function getInstance($options = array())
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self($options);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Daemon_Collector constructor.
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
private function __construct($options = array())
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function initialize_daemon_collector() {
|
||||
if (!$this->code) {
|
||||
$this->buildHeader()
|
||||
->buildParams()
|
||||
->buildFooter();
|
||||
}
|
||||
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function buildHeader() {
|
||||
$header = <<<EOF
|
||||
<script type="text/javascript">
|
||||
(function(_,r,e,t,a,i,l){_['retailCRMObject']=a;_[a]=_[a]||function(){(_[a].q=_[a].q||[]).push(arguments)};_[a].l=1*new Date();l=r.getElementsByTagName(e)[0];i=r.createElement(e);i.async=!0;i.src=t;l.parentNode.insertBefore(i,l)})(window,document,'script','https://collector.retailcrm.pro/w.js','_rc');
|
||||
|
||||
EOF;
|
||||
|
||||
$this->code .= $header;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function buildParams() {
|
||||
$params = array();
|
||||
|
||||
if (
|
||||
function_exists('WC')
|
||||
&& WC()->customer !== null
|
||||
&& WC()->customer->get_id() > 0
|
||||
) {
|
||||
$params['customerId'] = WC()->customer->get_id();
|
||||
}
|
||||
|
||||
$this->code .= apply_filters('retailcrm_daemon_collector', '') . sprintf(
|
||||
"\t_rc('create', '%s', %s);\n",
|
||||
$this->options['daemon_collector_key'],
|
||||
json_encode((object) $params)
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function buildFooter() {
|
||||
$footer = <<<EOF
|
||||
_rc('send', 'pageView');
|
||||
</script>
|
||||
|
||||
EOF;
|
||||
|
||||
$this->code .= $footer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
135
src/include/class-wc-retailcrm-ga.php
Normal file
135
src/include/class-wc-retailcrm-ga.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Google_Analytics')) {
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Google_Analytics - Integration with Google Analytics.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Google_Analytics {
|
||||
private static $instance;
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return WC_Retailcrm_Google_Analytics
|
||||
*/
|
||||
public static function getInstance($options = array())
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self($options);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Google_Analytics constructor.
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
private function __construct($options = array())
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function initialize_analytics() {
|
||||
return apply_filters('retailcrm_initialize_analytics' ,"
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '" . $this->options['ua_code'] . "', 'auto');
|
||||
|
||||
function getRetailCrmCookie(name) {
|
||||
var matches = document.cookie.match(new RegExp(
|
||||
'(?:^|; )' + name + '=([^;]*)'
|
||||
));
|
||||
return matches ? decodeURIComponent(matches[1]) : '';
|
||||
}
|
||||
|
||||
ga('set', 'dimension" . $this->options['ua_custom'] ."', getRetailCrmCookie('_ga'));
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function send_analytics() {
|
||||
$js = '';
|
||||
|
||||
if (!isset($_GET['key'])) {
|
||||
return $js;
|
||||
}
|
||||
|
||||
$order_id = wc_get_order_id_by_order_key($_GET['key']);
|
||||
$order = wc_get_order($order_id);
|
||||
|
||||
if (is_object($order) === false) {
|
||||
return $js;
|
||||
}
|
||||
|
||||
foreach ($order->get_items() as $item) {
|
||||
$uid = ($item['variation_id'] > 0) ? $item['variation_id'] : $item['product_id'];
|
||||
$_product = wc_get_product($uid);
|
||||
|
||||
if ($_product) {
|
||||
$order_item = array(
|
||||
'id' => $uid,
|
||||
'name' => $item['name'],
|
||||
'price' => (float)$_product->get_price(),
|
||||
'quantity' => $item['qty'],
|
||||
);
|
||||
|
||||
$order_items[] = $order_item;
|
||||
}
|
||||
}
|
||||
|
||||
$url = parse_url(get_site_url());
|
||||
$domain = $url['host'];
|
||||
|
||||
$js .= "
|
||||
<script type=\"text/javascript\">
|
||||
ga('require', 'ecommerce', 'ecommerce.js');
|
||||
ga('ecommerce:addTransaction', {
|
||||
'id':" . $order->get_id() . ",
|
||||
'affiliation':'" . $domain . "',
|
||||
'revenue':" . $order->get_total() . ",
|
||||
'shipping':" . $order->get_total_tax() . ",
|
||||
'tax':" . $order->get_shipping_total() . "
|
||||
});
|
||||
";
|
||||
|
||||
foreach ($order_items as $item) {
|
||||
$js .= "
|
||||
ga('ecommerce:addItem', {
|
||||
'id':" . $order->get_id() . ",
|
||||
'sku':" . $item['id'] . ",
|
||||
'name': '" . $item['name'] . "',
|
||||
'price': " . $item['price'] . ",
|
||||
'quantity': " . $item['quantity'] . "
|
||||
});
|
||||
";
|
||||
}
|
||||
|
||||
$js .= "ga('ecommerce:send');
|
||||
</script>";
|
||||
|
||||
return apply_filters('retailcrm_send_analytics', $js);
|
||||
}
|
||||
}
|
||||
}
|
1595
src/include/class-wc-retailcrm-history.php
Normal file
1595
src/include/class-wc-retailcrm-history.php
Normal file
File diff suppressed because it is too large
Load diff
403
src/include/class-wc-retailcrm-icml.php
Normal file
403
src/include/class-wc-retailcrm-icml.php
Normal file
|
@ -0,0 +1,403 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Icml')) :
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Icml - Generate ICML file (catalog).
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Icml
|
||||
{
|
||||
const OFFER_PROPERTIES = [
|
||||
'name',
|
||||
'productName',
|
||||
'price',
|
||||
'purchasePrice',
|
||||
'vendor',
|
||||
'picture',
|
||||
'url',
|
||||
'xmlId',
|
||||
'productActivity'
|
||||
];
|
||||
|
||||
protected $shop;
|
||||
|
||||
protected $file;
|
||||
|
||||
protected $tmpFile;
|
||||
|
||||
protected $settings;
|
||||
|
||||
protected $icmlWriter;
|
||||
|
||||
protected $unloadServices = false;
|
||||
|
||||
protected $activeLoyalty = false;
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Icml constructor.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->shop = get_bloginfo('name');
|
||||
$this->file = ABSPATH . 'simla.xml';
|
||||
$this->tmpFile = sprintf('%s.tmp', $this->file);
|
||||
$this->settings = get_option(WC_Retailcrm_Base::$option_key);
|
||||
$this->icmlWriter = new WC_Retailcrm_Icml_Writer($this->tmpFile);
|
||||
$this->unloadServices = (
|
||||
isset($this->settings['icml_unload_services'])
|
||||
&& $this->settings['icml_unload_services'] === WC_Retailcrm_Base::YES
|
||||
);
|
||||
|
||||
if (isset($this->settings['loyalty']) && $this->settings['loyalty'] === WC_Retailcrm_Base::YES) {
|
||||
$this->activeLoyalty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function changeBindBySku($useXmlId)
|
||||
{
|
||||
$this->settings['bind_by_sku'] = $useXmlId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ICML catalog.
|
||||
*/
|
||||
public function generate()
|
||||
{
|
||||
$this->icmlWriter->writeHead($this->shop);
|
||||
|
||||
$categories = $this->prepareCategories();
|
||||
|
||||
if (empty($categories)) {
|
||||
WC_Retailcrm_Logger::error(__METHOD__, 'Can`t get categories!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->icmlWriter->writeCategories($categories);
|
||||
|
||||
$offers = $this->prepareOffers();
|
||||
|
||||
if (empty($offers)) {
|
||||
WC_Retailcrm_Logger::error(__METHOD__, 'Can`t get offers!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->icmlWriter->writeOffers($offers);
|
||||
|
||||
$this->icmlWriter->writeEnd();
|
||||
$this->icmlWriter->formatXml($this->tmpFile);
|
||||
|
||||
rename($this->tmpFile, $this->file);
|
||||
WC_Retailcrm_Logger::info(__METHOD__, 'Catalog generated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare WC offers for write.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function prepareOffers()
|
||||
{
|
||||
$productStatuses = $this->getProductStatuses();
|
||||
|
||||
if (!$productStatuses) {
|
||||
$productStatuses = ['publish'];
|
||||
}
|
||||
|
||||
$page = 1;
|
||||
$offerAttributes = $this->getOfferAttributes();
|
||||
|
||||
do {
|
||||
$products = wc_get_products(
|
||||
[
|
||||
'limit' => 1000,
|
||||
'status' => $productStatuses,
|
||||
'page' => $page,
|
||||
'paginate' => true,
|
||||
]
|
||||
);
|
||||
|
||||
// Clearing the object cache after calling the function wc_get_products
|
||||
wp_cache_flush();
|
||||
|
||||
if (empty($products)) {
|
||||
WC_Retailcrm_Logger::error(__METHOD__, 'Can`t get products!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($products->products as $offer) {
|
||||
$type = $offer->get_type();
|
||||
|
||||
if (strpos($type, 'variable') !== false || strpos($type, 'variation') !== false) {
|
||||
foreach ($offer->get_children() as $childId) {
|
||||
$childProduct = wc_get_product($childId);
|
||||
|
||||
if (!$childProduct) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield $this->getOffer($offerAttributes, $childProduct, $offer);
|
||||
}
|
||||
} else {
|
||||
yield $this->getOffer($offerAttributes, $offer);
|
||||
}
|
||||
}
|
||||
|
||||
$page++;
|
||||
} while ($page <= $products->max_num_pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WC offer attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getOfferAttributes()
|
||||
{
|
||||
$offerAttributes = [];
|
||||
$attributeTaxonomies = wc_get_attribute_taxonomies();
|
||||
|
||||
foreach ($attributeTaxonomies as $productAttribute) {
|
||||
$attributeId = wc_attribute_taxonomy_name_by_id(intval($productAttribute->attribute_id));
|
||||
$offerAttributes[$attributeId] = $productAttribute->attribute_label;
|
||||
}
|
||||
|
||||
return $offerAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offer for ICML catalog
|
||||
*
|
||||
* @param array $productAttributes
|
||||
* @param WC_Product $product
|
||||
* @param bool | WC_Product_Variable $parent
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getOffer(array $productAttributes, WC_Product $product, $parent = false)
|
||||
{
|
||||
$idImages = array_merge([$product->get_image_id()], $product->get_gallery_image_ids());
|
||||
|
||||
if ($parent !== false && empty(get_the_post_thumbnail_url($product->get_id()))) {
|
||||
$idImages = array_merge([$parent->get_image_id()], $parent->get_gallery_image_ids());
|
||||
}
|
||||
|
||||
$images = [];
|
||||
|
||||
foreach ($idImages as $id) {
|
||||
$attachmentImageSrc = wp_get_attachment_image_src($id, 'full');
|
||||
|
||||
if (is_array($attachmentImageSrc) && isset($attachmentImageSrc[0])) {
|
||||
$images[] = $attachmentImageSrc[0];
|
||||
}
|
||||
}
|
||||
|
||||
$termList = $parent !== false
|
||||
? $parent->get_category_ids()
|
||||
: $product->get_category_ids();
|
||||
|
||||
$attributes = $parent !== false
|
||||
? get_post_meta($parent->get_id(), '_product_attributes')
|
||||
: get_post_meta($product->get_id(), '_product_attributes');
|
||||
|
||||
// All attributes are in the first element of the array
|
||||
$attributes = (isset($attributes[0])) ? $attributes[0] : $attributes;
|
||||
|
||||
$params = [];
|
||||
|
||||
if (!empty($attributes)) {
|
||||
foreach ($attributes as $attributeName => $attribute) {
|
||||
$attributeValue = $product->get_attribute($attributeName);
|
||||
if ($attribute['is_visible'] == 1 && !empty($attributeValue)) {
|
||||
$params[] = [
|
||||
'code' => $attributeName,
|
||||
'name' => $productAttributes[$attributeName],
|
||||
'value' => $attributeValue
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dimensions = '';
|
||||
|
||||
if ($product->get_length() !== '') {
|
||||
$dimensions = wc_get_dimension($product->get_length(), 'cm');
|
||||
}
|
||||
|
||||
if ($product->get_width() !== '') {
|
||||
$dimensions .= '/' . wc_get_dimension($product->get_width(), 'cm');
|
||||
}
|
||||
|
||||
if ($product->get_height() !== '') {
|
||||
$dimensions .= '/' . wc_get_dimension($product->get_height(), 'cm');
|
||||
}
|
||||
|
||||
$weight = '';
|
||||
|
||||
if ($product->get_weight() !== '') {
|
||||
$weight = wc_get_weight($product->get_weight(), 'kg');
|
||||
}
|
||||
|
||||
if ($product->is_taxable()) {
|
||||
$tax_rates = WC_Tax::get_rates($product->get_tax_class());
|
||||
$tax = reset($tax_rates);
|
||||
}
|
||||
|
||||
$productData = [
|
||||
'id' => $product->get_id(),
|
||||
'productId' => ($product->get_parent_id() > 0) ? $parent->get_id() : $product->get_id(),
|
||||
'name' => $product->get_name(),
|
||||
'productName' => ($product->get_parent_id() > 0) ? $parent->get_title() : $product->get_title(),
|
||||
'price' => $this->activeLoyalty
|
||||
? wc_get_price_including_tax($product, ["price" => $product->get_regular_price()])
|
||||
: wc_get_price_including_tax($product)
|
||||
,
|
||||
'picture' => $images,
|
||||
'url' => ($product->get_parent_id() > 0) ? $parent->get_permalink() : $product->get_permalink(),
|
||||
'quantity' => $this->getQuantity($product),
|
||||
'categoryId' => $termList,
|
||||
'dimensions' => $dimensions,
|
||||
'weight' => $weight,
|
||||
'tax' => isset($tax) ? $tax['rate'] : 'none',
|
||||
'type' => ($this->unloadServices && $product->is_virtual()) ? 'service' : 'product',
|
||||
];
|
||||
|
||||
if ($product->get_sku() !== '') {
|
||||
$params[] = ['code' => 'article', 'name' => 'Article', 'value' => $product->get_sku()];
|
||||
|
||||
if (isset($this->settings['bind_by_sku']) && $this->settings['bind_by_sku'] == WC_Retailcrm_Base::YES) {
|
||||
$productData['xmlId'] = $product->get_sku();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->settings['product_description'])) {
|
||||
$productDescription = $this->getOfferDescription($product);
|
||||
|
||||
if (empty($productDescription) && $parent instanceof WC_Product_Variable) {
|
||||
$this->getOfferDescription($parent);
|
||||
}
|
||||
|
||||
if ($productDescription != '') {
|
||||
$params[] = ['code' => 'description', 'name' => 'Description', 'value' => $productDescription];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($params)) {
|
||||
$productData['params'] = $params;
|
||||
}
|
||||
|
||||
$productData = apply_filters(
|
||||
'retailcrm_process_offer',
|
||||
WC_Retailcrm_Plugin::clearArray($productData),
|
||||
$product
|
||||
);
|
||||
|
||||
return $productData ?? [];
|
||||
}
|
||||
|
||||
private function getQuantity($product)
|
||||
{
|
||||
if ($product->get_manage_stock()) {
|
||||
$stockQuantity = $product->get_stock_quantity();
|
||||
$quantity = $stockQuantity !== null && $stockQuantity !== 0 ? $stockQuantity : 0;
|
||||
} else {
|
||||
$quantity = $product->get_stock_status() === 'instock' ? 1 : 0;
|
||||
}
|
||||
|
||||
return $quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product statuses.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getProductStatuses()
|
||||
{
|
||||
$statuses = [];
|
||||
|
||||
foreach (get_post_statuses() as $key => $value) {
|
||||
if (isset($this->settings['p_' . $key]) && $this->settings['p_' . $key] == WC_Retailcrm_Base::YES) {
|
||||
$statuses[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offer description.
|
||||
*
|
||||
* @param WC_Product | WC_Product_Variable $product WC product.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getOfferDescription($product)
|
||||
{
|
||||
return $this->settings['product_description'] == 'full'
|
||||
? $product->get_description()
|
||||
: $product->get_short_description();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare WC categories for write.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareCategories()
|
||||
{
|
||||
$categories = [];
|
||||
$taxonomy = 'product_cat';
|
||||
$orderby = 'parent';
|
||||
$show_count = 0; // 1 for yes, 0 for no
|
||||
$pad_counts = 0; // 1 for yes, 0 for no
|
||||
$hierarchical = 1; // 1 for yes, 0 for no
|
||||
$title = '';
|
||||
$empty = 0;
|
||||
|
||||
$args = [
|
||||
'taxonomy' => $taxonomy,
|
||||
'orderby' => $orderby,
|
||||
'show_count' => $show_count,
|
||||
'pad_counts' => $pad_counts,
|
||||
'hierarchical' => $hierarchical,
|
||||
'title_li' => $title,
|
||||
'hide_empty' => $empty
|
||||
];
|
||||
|
||||
$wcTerms = get_categories($args);
|
||||
|
||||
foreach ($wcTerms as $term) {
|
||||
$category = [
|
||||
'id' => $term->term_id,
|
||||
'parentId' => $term->parent,
|
||||
'name' => $term->name
|
||||
];
|
||||
|
||||
$thumbnailId = function_exists('get_term_meta')
|
||||
? get_term_meta($term->term_id, 'thumbnail_id', true)
|
||||
: get_woocommerce_term_meta($term->term_id, 'thumbnail_id', true);
|
||||
|
||||
$picture = wp_get_attachment_url($thumbnailId);
|
||||
|
||||
if ($picture) {
|
||||
$category['picture'] = $picture;
|
||||
}
|
||||
|
||||
$categories[] = $category;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
}
|
||||
endif;
|
142
src/include/class-wc-retailcrm-inventories.php
Normal file
142
src/include/class-wc-retailcrm-inventories.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Inventories')) :
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Inventories - Allows manage stocks.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Inventories
|
||||
{
|
||||
/** @var WC_Retailcrm_Client_V5 */
|
||||
protected $retailcrm;
|
||||
|
||||
/** @var array */
|
||||
protected $crmSettings;
|
||||
|
||||
/** @var string */
|
||||
protected $bindField = 'externalId';
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Inventories constructor.
|
||||
* @param bool $retailcrm
|
||||
*/
|
||||
public function __construct($retailcrm = false)
|
||||
{
|
||||
$this->crmSettings = get_option(WC_Retailcrm_Base::$option_key);
|
||||
$this->retailcrm = $retailcrm;
|
||||
|
||||
if (!empty($this->crmSettings['bind_by_sku']) && $this->crmSettings['bind_by_sku'] === WC_Retailcrm_Base::YES) {
|
||||
$this->bindField = 'xmlId';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load stock from RetailCRM
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function load_stocks()
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$page = 1;
|
||||
$availableStores = $this->crmSettings['stores_for_uploading'] ?? null;
|
||||
$variationProducts = [];
|
||||
|
||||
do {
|
||||
/** @var WC_Retailcrm_Response $response */
|
||||
$response = $this->retailcrm->storeInventories(['details' => true], $page, 250);
|
||||
|
||||
if (empty($response['offers']) || !$response->isSuccessful()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$totalPageCount = $response['pagination']['totalPageCount'];
|
||||
$page++;
|
||||
|
||||
foreach ($response['offers'] as $offer) {
|
||||
$offerQuantity = $offer['quantity'];
|
||||
|
||||
if (!empty($availableStores) && count($offer['stores']) > 1) {
|
||||
$offerQuantity = 0;
|
||||
|
||||
foreach ($offer['stores'] as $store) {
|
||||
if (in_array($store['store'], $availableStores, true)) {
|
||||
$offerQuantity += $store['quantity'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($offer[$this->bindField])) {
|
||||
$product = retailcrm_get_wc_product($offer[$this->bindField], $this->crmSettings);
|
||||
|
||||
if ($product instanceof WC_Product) {
|
||||
if ($product->get_type() === 'external') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($product->get_type() == 'variation' || $product->get_type() == 'variable') {
|
||||
$parentId = $product->get_parent_id();
|
||||
|
||||
if (!empty($parentId)) {
|
||||
if (isset($variationProducts[$parentId])) {
|
||||
$variationProducts[$parentId] += $offerQuantity;
|
||||
} else {
|
||||
$variationProducts[$parentId] = $offerQuantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$product->set_manage_stock(true);
|
||||
$product->set_stock_quantity($offerQuantity);
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clearing the object cache after calling the function wc_get_products
|
||||
wp_cache_flush();
|
||||
} while ($page <= $totalPageCount);
|
||||
|
||||
if (!empty($variationProducts)) {
|
||||
$chunks = array_chunk($variationProducts, 100, true);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
foreach ($chunk as $id => $quantity) {
|
||||
$variationProduct = wc_get_product($id);
|
||||
|
||||
if (is_object($variationProduct)) {
|
||||
$variationProduct->set_manage_stock(true);
|
||||
$variationProduct->set_stock_quantity($quantity);
|
||||
$variationProduct->save();
|
||||
}
|
||||
}
|
||||
|
||||
wp_cache_flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stock quantity in WooCommerce
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateQuantity()
|
||||
{
|
||||
if ($this->crmSettings['sync'] === WC_Retailcrm_Base::YES) {
|
||||
$this->load_stocks();
|
||||
}
|
||||
}
|
||||
}
|
||||
endif;
|
||||
|
583
src/include/class-wc-retailcrm-loyalty.php
Normal file
583
src/include/class-wc-retailcrm-loyalty.php
Normal file
|
@ -0,0 +1,583 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Loyalty')) :
|
||||
if (!class_exists('WC_Retailcrm_Loyalty_Form')) {
|
||||
include_once(WC_Integration_Retailcrm::checkCustomFile('include/components/class-wc-retailcrm-loyalty-form.php'));
|
||||
}
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Loyalty - Allows transfer data carts with CMS.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Loyalty
|
||||
{
|
||||
/** @var WC_Retailcrm_Client_V5 */
|
||||
protected $apiClient;
|
||||
|
||||
protected $dateFormat;
|
||||
|
||||
protected $settings;
|
||||
|
||||
/** @var WC_Retailcrm_Loyalty_Form */
|
||||
protected $loyaltyForm;
|
||||
|
||||
protected $validator;
|
||||
|
||||
public function __construct($apiClient, $settings)
|
||||
{
|
||||
$this->apiClient = $apiClient;
|
||||
$this->settings = $settings;
|
||||
$this->dateFormat = 'Y-m-d H:i:sP';
|
||||
$this->loyaltyForm = new WC_Retailcrm_Loyalty_Form();
|
||||
$this->validator = new WC_Retailcrm_Loyalty_Validator(
|
||||
$this->apiClient,
|
||||
isCorporateUserActivate($this->settings)
|
||||
);
|
||||
}
|
||||
|
||||
public function getForm(int $userId, $loyaltyTerms = '', $loyaltyPersonal = '')
|
||||
{
|
||||
$result = [];
|
||||
$phone = '';
|
||||
|
||||
$wcCustomer = new WC_Customer($userId);
|
||||
|
||||
if ($wcCustomer instanceof WC_Customer) {
|
||||
$phone = $wcCustomer->get_billing_phone();
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->getLoyaltyAccounts($userId);
|
||||
} catch (Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(
|
||||
__METHOD__,
|
||||
$exception,
|
||||
'Exception get loyalty accounts: '
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$loyaltyAccount = $response['loyaltyAccounts'][0] ?? null;
|
||||
|
||||
if ($loyaltyAccount && (int) $loyaltyAccount['customer']['externalId'] === $userId) {
|
||||
if ($loyaltyAccount['active'] === true) {
|
||||
$result['form'] = $this->loyaltyForm->getInfoLoyalty($loyaltyAccount);
|
||||
} else {
|
||||
$result['form'] = $this->loyaltyForm->getActivationForm();
|
||||
|
||||
$result['loyaltyId'] = $loyaltyAccount['id'];
|
||||
}
|
||||
} else {
|
||||
$result['form'] = $this->loyaltyForm->getRegistrationForm($phone, $loyaltyTerms, $loyaltyPersonal);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function registerCustomer(int $userId, string $phone, string $site): bool
|
||||
{
|
||||
$parameters = [
|
||||
'phoneNumber' => $phone,
|
||||
'customer' => [
|
||||
'externalId' => $userId
|
||||
]
|
||||
];
|
||||
|
||||
try {
|
||||
$wcCustomer = new WC_Customer($userId);
|
||||
|
||||
if ($wcCustomer instanceof WC_Customer) {
|
||||
$currentPhone = $wcCustomer->get_billing_phone();
|
||||
|
||||
if (empty($currentPhone) && $phone !== '') {
|
||||
$wcCustomer->set_billing_phone($phone);
|
||||
$wcCustomer->save();
|
||||
}
|
||||
}
|
||||
|
||||
$response = $this->apiClient->createLoyaltyAccount($parameters, $site);
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
WC_Retailcrm_Logger::error(
|
||||
__METHOD__,
|
||||
'Error while registering in the loyalty program',
|
||||
['response' => json_decode($response->getRawResponse(), true)]
|
||||
);
|
||||
}
|
||||
|
||||
return $response->isSuccessful();
|
||||
} catch (Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(
|
||||
__METHOD__,
|
||||
$exception,
|
||||
'Exception while registering in the loyalty program: '
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function activateLoyaltyCustomer(int $loyaltyId)
|
||||
{
|
||||
try {
|
||||
$response = $this->apiClient->activateLoyaltyAccount($loyaltyId);
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
WC_Retailcrm_Logger::error(
|
||||
__METHOD__,
|
||||
'Error while registering in the loyalty program',
|
||||
['response' => json_decode($response->getRawResponse(), true)]
|
||||
);
|
||||
}
|
||||
|
||||
return $response->isSuccessful();
|
||||
} catch (Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(__METHOD__, $exception);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function getDiscountLoyalty($cartItems, $site, $customerId)
|
||||
{
|
||||
$response = $this->calculateDiscountLoyalty($cartItems, $site, $customerId);
|
||||
|
||||
if ($response === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$discount = 0;
|
||||
|
||||
//Checking if the loyalty discount is a percent discount
|
||||
foreach ($response['order']['items'] as $item) {
|
||||
if (!isset($item['discounts'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($item['discounts'] as $discountItem) {
|
||||
if ($discountItem['type'] === 'loyalty_level') {
|
||||
$discount += $discountItem['amount'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If the discount has already been given, do not work with points deduction
|
||||
if ($discount === 0) {
|
||||
foreach ($response['calculations'] as $calculate) {
|
||||
if ($calculate['privilegeType'] !== 'loyalty_level') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$discount = $calculate['maxChargeBonuses'];
|
||||
}
|
||||
}
|
||||
|
||||
return $discount;
|
||||
}
|
||||
|
||||
private function getLoyaltyAccounts(int $userId)
|
||||
{
|
||||
$response = $this->apiClient->customersGet($userId);
|
||||
|
||||
if (!isset($response['customer']['id'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$filter['customerId'] = $response['customer']['id'];
|
||||
|
||||
$response = $this->apiClient->getLoyaltyAccountList($filter);
|
||||
|
||||
if (!$response->isSuccessful() || !$response->offsetExists('loyaltyAccounts')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function createLoyaltyCoupon($refreshCoupon = false)
|
||||
{
|
||||
global $woocommerce;
|
||||
|
||||
$site = $this->apiClient->getSingleSiteForKey();
|
||||
$cartItems = $woocommerce->cart->get_cart();
|
||||
$customerId = $woocommerce->customer ? $woocommerce->customer->get_id() : null;
|
||||
|
||||
$resultString = '';
|
||||
|
||||
if (!$customerId || !$cartItems) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$couponsLp = [];
|
||||
// Check exists used loyalty coupons
|
||||
foreach ($woocommerce->cart->get_coupons() as $code => $coupon) {
|
||||
if ($this->isLoyaltyCoupon($code)) {
|
||||
$couponsLp[] = $code;
|
||||
}
|
||||
}
|
||||
|
||||
// if you need to refresh coupon that does not exist
|
||||
if (count($couponsLp) === 0 && $refreshCoupon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//If one loyalty coupon is used, not generate a new one
|
||||
if (count($couponsLp) === 1 && !$refreshCoupon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if more than 1 loyalty coupon is used, delete all coupons
|
||||
if (count($couponsLp) > 1 || $refreshCoupon) {
|
||||
foreach ($couponsLp as $code) {
|
||||
$woocommerce->cart->remove_coupon($code);
|
||||
|
||||
$coupon = new WC_Coupon($code);
|
||||
|
||||
$coupon->delete(true);
|
||||
}
|
||||
|
||||
$woocommerce->cart->calculate_totals();
|
||||
}
|
||||
|
||||
if (!$this->validator->checkAccount($customerId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lpDiscountSum = $this->getDiscountLoyalty($woocommerce->cart->get_cart(), $site, $customerId);
|
||||
|
||||
if ($lpDiscountSum === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Check the existence of loyalty coupons and delete them
|
||||
$coupons = $this->getCouponLoyalty($woocommerce->customer->get_email());
|
||||
|
||||
foreach ($coupons as $item) {
|
||||
$coupon = new WC_Coupon($item['code']);
|
||||
|
||||
$coupon->delete(true);
|
||||
}
|
||||
|
||||
//Generate new coupon
|
||||
$coupon = new WC_Coupon();
|
||||
|
||||
$coupon->set_usage_limit(0);
|
||||
$coupon->set_amount($lpDiscountSum);
|
||||
$coupon->set_email_restrictions($woocommerce->customer->get_email());
|
||||
$coupon->set_code('loyalty' . mt_rand());
|
||||
$coupon->save();
|
||||
|
||||
if ($refreshCoupon) {
|
||||
$woocommerce->cart->apply_coupon($coupon->get_code());
|
||||
|
||||
return $resultString;
|
||||
}
|
||||
|
||||
//If a percentage discount, automatically apply a loyalty coupon
|
||||
if ($this->validator->loyaltyAccount['level']['type'] === 'discount') {
|
||||
$woocommerce->cart->apply_coupon($coupon->get_code());
|
||||
|
||||
return $resultString;
|
||||
}
|
||||
|
||||
$resultString .= ' <div style="text-align: left; line-height: 3"><b>' . __('It is possible to write off', 'retailcrm') . ' ' . $lpDiscountSum . ' ' . __('bonuses', 'retailcrm') . '</b></div>';
|
||||
return $resultString. '<div style="text-align: left;"><b>' . __('Use coupon:', 'retailcrm') . ' <u><i style="cursor: grab" id="input_loyalty_code" onclick="inputLoyaltyCode()">' . $coupon->get_code() . '</i></u></i></b></div>';
|
||||
}
|
||||
|
||||
public function clearLoyaltyCoupon()
|
||||
{
|
||||
global $woocommerce;
|
||||
|
||||
foreach ($woocommerce->cart->get_coupons() as $code => $coupon) {
|
||||
if ($this->isLoyaltyCoupon($code)) {
|
||||
$woocommerce->cart->remove_coupon($code);
|
||||
|
||||
$coupon = new WC_Coupon($code);
|
||||
|
||||
$coupon->delete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteLoyaltyCoupon($couponCode)
|
||||
{
|
||||
if ($this->isLoyaltyCoupon($couponCode)) {
|
||||
$coupon = new WC_Coupon($couponCode);
|
||||
|
||||
$coupon->delete(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isLoyaltyCoupon($couponCode): bool
|
||||
{
|
||||
return preg_match('/^loyalty\d+$/m', $couponCode) === 1;
|
||||
}
|
||||
|
||||
public function getCouponLoyalty($email)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT posts.post_name code FROM {$wpdb->prefix}posts AS posts
|
||||
LEFT JOIN {$wpdb->prefix}postmeta AS postmeta ON posts.ID = postmeta.post_id
|
||||
WHERE posts.post_type = 'shop_coupon' AND posts.post_name LIKE 'loyalty%'
|
||||
AND postmeta.meta_key = 'customer_email' AND postmeta.meta_value LIKE %s",
|
||||
'%' . $email . '%'
|
||||
), ARRAY_A
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteLoyaltyCouponInOrder($wcOrder)
|
||||
{
|
||||
$discountLp = 0;
|
||||
$coupons = $wcOrder->get_coupons();
|
||||
|
||||
foreach ($coupons as $coupon) {
|
||||
$code = $coupon->get_code();
|
||||
|
||||
if ($this->isLoyaltyCoupon($code)) {
|
||||
$discountLp = $coupon->get_discount();
|
||||
$wcOrder->remove_coupon($code);
|
||||
$objectCoupon = new WC_Coupon($code);
|
||||
$objectCoupon->delete(true);
|
||||
|
||||
$wcOrder->recalculate_coupons();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $discountLp;
|
||||
}
|
||||
|
||||
public function isValidOrder($wcCustomer, $wcOrder)
|
||||
{
|
||||
return !(!$wcCustomer || (isCorporateUserActivate($this->settings) && isCorporateOrder($wcCustomer, $wcOrder)));
|
||||
}
|
||||
|
||||
public function isValidUser($wcCustomer)
|
||||
{
|
||||
if (empty($wcCustomer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->getLoyaltyAccounts($wcCustomer->get_id());
|
||||
} catch (Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(
|
||||
__METHOD__,
|
||||
$exception,
|
||||
'Exception get loyalty accounts: '
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($response['loyaltyAccounts'][0]);
|
||||
}
|
||||
|
||||
public function applyLoyaltyDiscount($wcOrder, $createdOrder, $discountLp = 0)
|
||||
{
|
||||
$isPercentDiscount = false;
|
||||
$items = [];
|
||||
|
||||
// Verification of automatic creation of the percentage discount of the loyalty program
|
||||
foreach ($createdOrder['items'] as $item) {
|
||||
foreach ($item['discounts'] as $discount) {
|
||||
if ($discount['type'] === 'loyalty_level') {
|
||||
$isPercentDiscount = true;
|
||||
$items = $createdOrder['items'];
|
||||
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isPercentDiscount) {
|
||||
$response = $this->apiClient->applyBonusToOrder($createdOrder['site'], ['externalId' => $createdOrder['externalId']], (float) $discountLp);
|
||||
|
||||
if (!$response instanceof WC_Retailcrm_Response || !$response->isSuccessful()) {
|
||||
return $response->getErrorString();
|
||||
}
|
||||
|
||||
$items = $response['order']['items'];
|
||||
}
|
||||
|
||||
$this->calculateLoyaltyDiscount($wcOrder, $items);
|
||||
}
|
||||
|
||||
public function calculateLoyaltyDiscount($wcOrder, $orderItems)
|
||||
{
|
||||
$wcItems = $wcOrder->get_items();
|
||||
|
||||
foreach ($orderItems as $item) {
|
||||
$externalId = $item['externalIds'][0]['value'];
|
||||
$externalId = preg_replace('/^\d+\_/m', '', $externalId);
|
||||
|
||||
if (isset($wcItems[(int) $externalId])) {
|
||||
$discountLoyaltyTotal = 0;
|
||||
|
||||
foreach ($item['discounts'] as $discount) {
|
||||
if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) {
|
||||
$discountLoyaltyTotal += $discount['amount'];
|
||||
}
|
||||
}
|
||||
|
||||
$wcItem = $wcItems[(int) $externalId];
|
||||
$wcItem->set_total($wcItem->get_total() - $discountLoyaltyTotal);
|
||||
$wcItem->calculate_taxes();
|
||||
$wcItem->save();
|
||||
}
|
||||
}
|
||||
|
||||
$wcOrder->calculate_totals();
|
||||
}
|
||||
|
||||
public function getCrmItemsInfo($orderExternalId)
|
||||
{
|
||||
$discountType = null;
|
||||
$crmItems = [];
|
||||
|
||||
$response = $this->apiClient->ordersGet($orderExternalId);
|
||||
|
||||
if (!$response instanceof WC_Retailcrm_Response || !$response->isSuccessful() || !isset($response['order'])) {
|
||||
WC_Retailcrm_Logger::error(
|
||||
__METHOD__,
|
||||
'Process order: Error when receiving an order from the CRM. Order Id: ' . $orderExternalId
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($response['order']['items'] as $item) {
|
||||
$externalId = $item['externalIds'][0]['value'];
|
||||
$externalId = preg_replace('/^\d+\_/m', '', $externalId);
|
||||
$crmItems[$externalId] = $item;
|
||||
|
||||
if (!$discountType) {
|
||||
foreach ($item['discounts'] as $discount) {
|
||||
if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) {
|
||||
$discountType = $discount['type'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['items' => $crmItems, 'discountType' => $discountType];
|
||||
}
|
||||
|
||||
public function calculateDiscountLoyalty($cartItems, $site, $customerId, $bonuses = 0)
|
||||
{
|
||||
$order = [
|
||||
'site' => $site,
|
||||
'customer' => ['externalId' => $customerId],
|
||||
'privilegeType' => 'loyalty_level'
|
||||
];
|
||||
|
||||
$useXmlId = isset($this->settings['bind_by_sku']) && $this->settings['bind_by_sku'] === WC_Retailcrm_Base::YES;
|
||||
|
||||
foreach ($cartItems as $item) {
|
||||
$product = $item['data'];
|
||||
|
||||
$productRegularPrice = wc_get_price_including_tax($product, ["price" => $product->get_regular_price()]);
|
||||
$discount = $productRegularPrice - ($item['line_total'] / $item['quantity']);
|
||||
|
||||
$order['items'][] = [
|
||||
'offer' => $useXmlId ? ['xmlId' => $product->get_sku()] : ['externalId' => $product->get_id()],
|
||||
'quantity' => $item['quantity'],
|
||||
'initialPrice' => $productRegularPrice,
|
||||
'discountManualAmount' => $discount
|
||||
];
|
||||
}
|
||||
|
||||
$response = $this->apiClient->calculateDiscountLoyalty($site, $order, (float) $bonuses);
|
||||
|
||||
if (!$response->isSuccessful() || !isset($response['calculations'])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function getCreditBonuses(): string
|
||||
{
|
||||
global $woocommerce;
|
||||
|
||||
$customerId = $woocommerce->customer ? $woocommerce->customer->get_id() : null;
|
||||
$site = $this->apiClient->getSingleSiteForKey();
|
||||
|
||||
if (
|
||||
!$customerId
|
||||
|| !$woocommerce->cart
|
||||
|| !$woocommerce->cart->get_cart()
|
||||
|| !$site
|
||||
|| !$this->validator->checkAccount($customerId)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$loyaltyCoupon = null;
|
||||
$coupons = $woocommerce->cart->get_applied_coupons();
|
||||
|
||||
foreach ($coupons as $coupon) {
|
||||
if ($this->isLoyaltyCoupon($coupon)) {
|
||||
$loyaltyCoupon = new WC_Coupon($coupon);
|
||||
|
||||
$woocommerce->cart->remove_coupon($coupon);
|
||||
$woocommerce->cart->calculate_totals();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($loyaltyCoupon) {
|
||||
$chargeBonuses = $loyaltyCoupon->get_amount();
|
||||
}
|
||||
|
||||
$cartItems = $woocommerce->cart->get_cart();
|
||||
$response = $this->calculateDiscountLoyalty($cartItems, $site, $customerId, $chargeBonuses ?? 0);
|
||||
|
||||
if ($loyaltyCoupon) {
|
||||
$coupon = new WC_Coupon();
|
||||
$coupon->set_usage_limit(0);
|
||||
$coupon->set_amount($loyaltyCoupon->get_amount());
|
||||
$coupon->set_email_restrictions($loyaltyCoupon->get_email_restrictions());
|
||||
$coupon->set_code($loyaltyCoupon->get_code());
|
||||
$coupon->save();
|
||||
|
||||
$woocommerce->cart->apply_coupon($coupon->get_code());
|
||||
$woocommerce->cart->calculate_totals();
|
||||
}
|
||||
|
||||
if ($response === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$creditBonuses = $response['order']['bonusesCreditTotal'];
|
||||
|
||||
if ($creditBonuses) {
|
||||
return $this->getHtmlCreditBonuses($creditBonuses);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getHtmlCreditBonuses($creditBonuses)
|
||||
{
|
||||
return '<b style="font-size: large">' . __("Points will be awarded upon completion of the order:", 'retailcrm') . ' <u style="color: green"><i>' . $creditBonuses . '</u></i></b>';
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
786
src/include/class-wc-retailcrm-orders.php
Normal file
786
src/include/class-wc-retailcrm-orders.php
Normal file
|
@ -0,0 +1,786 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Orders')) :
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Orders - Allows transfer data orders with CMS.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Orders
|
||||
{
|
||||
/** @var bool|WC_Retailcrm_Proxy|WC_Retailcrm_Client_V5 */
|
||||
protected $retailcrm;
|
||||
|
||||
/** @var WC_Retailcrm_Loyalty|null */
|
||||
protected $loyalty = null;
|
||||
|
||||
/** @var array */
|
||||
protected $retailcrm_settings;
|
||||
|
||||
/** @var WC_Retailcrm_Order_Item */
|
||||
protected $order_item;
|
||||
|
||||
/** @var WC_Retailcrm_Order_Address */
|
||||
protected $order_address;
|
||||
|
||||
/** @var WC_Retailcrm_Order_Payment */
|
||||
protected $order_payment;
|
||||
|
||||
/** @var WC_Retailcrm_Customers */
|
||||
protected $customers;
|
||||
|
||||
/** @var WC_Retailcrm_Order */
|
||||
protected $orders;
|
||||
|
||||
/** @var array */
|
||||
private $ordersGetRequestCache = [];
|
||||
|
||||
/** @var array */
|
||||
private $order = [];
|
||||
|
||||
/** @var bool */
|
||||
private $cancelLoyalty = false;
|
||||
|
||||
/** @var string */
|
||||
private $loyaltyDiscountType = '';
|
||||
|
||||
/** @var array */
|
||||
private $payment = [];
|
||||
|
||||
/**@var array */
|
||||
private $customFields = [];
|
||||
|
||||
public function __construct(
|
||||
$retailcrm,
|
||||
$retailcrm_settings,
|
||||
$order_item,
|
||||
$order_address,
|
||||
$customers,
|
||||
$orders,
|
||||
$order_payment
|
||||
) {
|
||||
$this->retailcrm = $retailcrm;
|
||||
$this->retailcrm_settings = $retailcrm_settings;
|
||||
$this->order_item = $order_item;
|
||||
$this->order_address = $order_address;
|
||||
$this->customers = $customers;
|
||||
$this->orders = $orders;
|
||||
$this->order_payment = $order_payment;
|
||||
|
||||
if (isLoyaltyActivate($retailcrm_settings)) {
|
||||
$this->loyalty = new WC_Retailcrm_Loyalty($retailcrm, $retailcrm_settings);
|
||||
}
|
||||
|
||||
if (!empty($retailcrm_settings['order-meta-data-retailcrm'])) {
|
||||
$this->customFields = json_decode($retailcrm_settings['order-meta-data-retailcrm'], true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create order. Returns wc_get_order data or error string.
|
||||
*
|
||||
* @param $orderId
|
||||
*
|
||||
* @return bool|WC_Order|string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function orderCreate($orderId)
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Start order creating ' . (is_int($orderId) ? $orderId : ''),
|
||||
['wc_order' => WC_Retailcrm_Logger::formatWcObject($orderId)]
|
||||
);
|
||||
|
||||
try {
|
||||
$this->order_payment->resetData();
|
||||
|
||||
$wcOrder = wc_get_order($orderId);
|
||||
|
||||
if ($this->loyalty) {
|
||||
$wcCustomer = null;
|
||||
$privilegeType = 'none';
|
||||
$discountLp = $this->loyalty->deleteLoyaltyCouponInOrder($wcOrder);
|
||||
$dataOrder = $wcOrder->get_data();
|
||||
|
||||
if (isset($dataOrder['customer_id'])) {
|
||||
$wcCustomer = new WC_Customer($dataOrder['customer_id']) ?? null;
|
||||
}
|
||||
|
||||
if (!$this->loyalty->isValidOrder($wcCustomer, $wcOrder)) {
|
||||
if ($discountLp > 0) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'The user does not meet the requirements for working with the loyalty program. Order Id: ' . $orderId
|
||||
);
|
||||
}
|
||||
|
||||
$discountLp = 0;
|
||||
$privilegeType = 'none';
|
||||
} elseif ($this->loyalty->isValidUser($wcCustomer)) {
|
||||
$privilegeType = 'loyalty_level';
|
||||
}
|
||||
}
|
||||
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Create WC_Order ' . $wcOrder->get_id(),
|
||||
['wc_order' => WC_Retailcrm_Logger::formatWcObject($wcOrder)]
|
||||
);
|
||||
$this->processOrder($wcOrder);
|
||||
|
||||
if (isset($privilegeType)) {
|
||||
$this->order['privilegeType'] = $privilegeType;
|
||||
}
|
||||
|
||||
$response = $this->retailcrm->ordersCreate($this->order);
|
||||
|
||||
// Allows you to verify order creation and perform additional actions
|
||||
$response = apply_filters('retailcrm_order_create_after', $response, $wcOrder);
|
||||
|
||||
if (!$response instanceof WC_Retailcrm_Response || !$response->isSuccessful()) {
|
||||
return $response->getErrorString();
|
||||
}
|
||||
|
||||
if (isset($discountLp) && $discountLp > 0) {
|
||||
$this->loyalty->applyLoyaltyDiscount($wcOrder, $response['order'], $discountLp);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(__METHOD__, $exception);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $wcOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process order customer data
|
||||
*
|
||||
* @param \WC_Order $wcOrder
|
||||
* @param bool $update
|
||||
*
|
||||
* @return bool Returns false if order cannot be processed
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processOrderCustomerInfo($wcOrder, $update = false)
|
||||
{
|
||||
$customerWasChanged = false;
|
||||
$wpUser = $wcOrder->get_user();
|
||||
|
||||
if ($update) {
|
||||
$response = $this->getCrmOrder($wcOrder->get_id());
|
||||
|
||||
if (!empty($response)) {
|
||||
$customerWasChanged = self::isOrderCustomerWasChanged($wcOrder, $response);
|
||||
}
|
||||
}
|
||||
|
||||
if ($wpUser instanceof WP_User) {
|
||||
if (!$this->customers->isCustomer($wpUser)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$wpUserId = (int) $wpUser->get('ID');
|
||||
|
||||
if (!$update || ($update && $customerWasChanged)) {
|
||||
$this->fillOrderCreate($wpUserId, $wpUser->get('billing_email'), $wcOrder);
|
||||
}
|
||||
} else {
|
||||
$wcCustomer = $this->customers->buildCustomerFromOrderData($wcOrder);
|
||||
|
||||
if (!$update || ($update && $customerWasChanged)) {
|
||||
$this->fillOrderCreate(0, $wcCustomer->get_billing_email(), $wcOrder);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill order on create
|
||||
*
|
||||
* @param int $wcCustomerId
|
||||
* @param string $wcCustomerEmail
|
||||
* @param \WC_Order $wcOrder
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function fillOrderCreate($wcCustomerId, $wcCustomerEmail, $wcOrder)
|
||||
{
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
sprintf(
|
||||
'Fill order data: WC_Customer ID: %s email: %s WC_Order ID: %s',
|
||||
$wcCustomerId,
|
||||
$wcCustomerEmail,
|
||||
$wcOrder->get_id()
|
||||
)
|
||||
);
|
||||
$isContact = $this->retailcrm->getCorporateEnabled() && static::isCorporateOrder($wcOrder);
|
||||
|
||||
$foundCustomer = $this->customers->findCustomerEmailOrId(
|
||||
$wcCustomerId,
|
||||
strtolower($wcCustomerEmail),
|
||||
$isContact
|
||||
);
|
||||
|
||||
if (empty($foundCustomer)) {
|
||||
$foundCustomerId = $this->customers->createCustomer($wcCustomerId, $wcOrder);
|
||||
|
||||
if (!empty($foundCustomerId)) {
|
||||
$this->order['customer']['id'] = $foundCustomerId;
|
||||
}
|
||||
} else {
|
||||
$this->order['customer']['id'] = $foundCustomer['id'];
|
||||
$foundCustomerId = $foundCustomer['id'];
|
||||
}
|
||||
|
||||
$this->order['contragent']['contragentType'] = 'individual';
|
||||
|
||||
if ($this->retailcrm->getCorporateEnabled() && static::isCorporateOrder($wcOrder)) {
|
||||
unset($this->order['contragent']['contragentType']);
|
||||
|
||||
$crmCorporate = $this->customers->searchCorporateCustomer(array(
|
||||
'contactIds' => array($foundCustomerId),
|
||||
'companyName' => $wcOrder->get_billing_company()
|
||||
));
|
||||
|
||||
if (empty($crmCorporate)) {
|
||||
$crmCorporate = $this->customers->searchCorporateCustomer(array(
|
||||
'companyName' => $wcOrder->get_billing_company()
|
||||
));
|
||||
}
|
||||
|
||||
if (empty($crmCorporate)) {
|
||||
$corporateId = $this->customers->createCorporateCustomerForOrder(
|
||||
$foundCustomerId,
|
||||
$wcCustomerId,
|
||||
$wcOrder
|
||||
);
|
||||
$this->order['customer']['id'] = $corporateId;
|
||||
} else {
|
||||
// Testing of this method occurs in customers tests.
|
||||
// @codeCoverageIgnoreStart
|
||||
$addressFound = $this->customers->fillCorporateAddress(
|
||||
$crmCorporate['id'],
|
||||
new WC_Customer($wcCustomerId),
|
||||
$wcOrder
|
||||
);
|
||||
|
||||
// If address not found create new address.
|
||||
if (!$addressFound) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Notification: Create new address for corporate customer ' . $this->order['customer']['id']
|
||||
);
|
||||
}
|
||||
|
||||
$this->order['customer']['id'] = $crmCorporate['id'];
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$companiesResponse = $this->retailcrm->customersCorporateCompanies(
|
||||
$this->order['customer']['id'],
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
'id'
|
||||
);
|
||||
|
||||
if (!empty($companiesResponse) && $companiesResponse->isSuccessful()) {
|
||||
foreach ($companiesResponse['companies'] as $company) {
|
||||
if ($company['name'] == $wcOrder->get_billing_company()) {
|
||||
$this->order['company'] = [
|
||||
'id' => $company['id'],
|
||||
'name' => $company['name']
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->order['contact']['id'] = $foundCustomerId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit order in CRM
|
||||
*
|
||||
* @param int $orderId
|
||||
*
|
||||
* @return WC_Order $order | null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateOrder($orderId, $statusTrash = false)
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$wcOrder = wc_get_order($orderId);
|
||||
|
||||
if ($wcOrder->get_status() === 'checkout-draft') {
|
||||
return null;
|
||||
}
|
||||
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Update WC_Order ' . $wcOrder->get_id(),
|
||||
['wc_order' => WC_Retailcrm_Logger::formatWcObject($wcOrder)]
|
||||
);
|
||||
$needRecalculate = false;
|
||||
|
||||
$this->processOrder($wcOrder, true, $statusTrash);
|
||||
|
||||
if ($this->cancelLoyalty) {
|
||||
$this->cancelLoyalty = false;
|
||||
$this->order_item->cancelLoyalty = false;
|
||||
$needRecalculate = true;
|
||||
|
||||
if ($this->loyaltyDiscountType === 'loyalty_level') {
|
||||
$this->order['privilegeType'] = 'none';
|
||||
}
|
||||
|
||||
if ($this->loyaltyDiscountType === 'bonus_charge') {
|
||||
$responseCancelBonus = $this->retailcrm->cancelBonusOrder(['externalId' => $this->order['externalId']]);
|
||||
|
||||
if (!$responseCancelBonus instanceof WC_Retailcrm_Response || !$responseCancelBonus->isSuccessful()) {
|
||||
WC_Retailcrm_Logger::error(__METHOD__, 'Error when canceling bonuses');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response = $this->retailcrm->ordersEdit($this->order);
|
||||
$response = apply_filters('retailcrm_order_update_after', $response, $wcOrder);
|
||||
|
||||
if ($response instanceof WC_Retailcrm_Response && $response->isSuccessful() && isset($response['order'])) {
|
||||
$this->payment = $this->orderUpdatePaymentType($wcOrder);
|
||||
|
||||
if ($needRecalculate) {
|
||||
$this->loyalty->calculateLoyaltyDiscount($wcOrder, $response['order']['items']);
|
||||
}
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(__METHOD__, $exception);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $wcOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update order payment type
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return null | array $payment
|
||||
*/
|
||||
protected function orderUpdatePaymentType($order)
|
||||
{
|
||||
if (!isset($this->retailcrm_settings[$order->get_payment_method()])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$retailcrmOrder = $this->getCrmOrder($order->get_id());
|
||||
|
||||
if (empty($retailcrmOrder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($retailcrmOrder['payments'] as $paymentData) {
|
||||
$paymentId = explode('-', $paymentData['externalId']);
|
||||
|
||||
if ($paymentId[0] == $order->get_id()) {
|
||||
$payment = $paymentData;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($payment)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($payment['type'] == $this->retailcrm_settings[$order->get_payment_method()] && $order->is_paid()) {
|
||||
return $this->sendPayment($order, true, $payment['externalId']);
|
||||
}
|
||||
|
||||
if ($payment['type'] != $this->retailcrm_settings[$order->get_payment_method()]) {
|
||||
$response = $this->retailcrm->ordersPaymentDelete($payment['id']);
|
||||
|
||||
if (!empty($response) && $response->isSuccessful()) {
|
||||
return $this->sendPayment($order);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* process to combine order data
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @param boolean $update
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processOrder($order, $update = false, $statusTrash = false)
|
||||
{
|
||||
if (!$order instanceof WC_Order) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('auto-draft' === $order->get_status()) {
|
||||
WC_Retailcrm_Logger::info(__METHOD__, 'Skip, order in auto-draft status');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($update) {
|
||||
$this->orders->is_new = false;
|
||||
}
|
||||
|
||||
$orderData = $this->orders->build($order)->getData();
|
||||
|
||||
if ($order->get_items('shipping')) {
|
||||
$shippings = $order->get_items('shipping');
|
||||
$shipping = reset($shippings);
|
||||
$shipping_code = explode(':', $shipping['method_id']);
|
||||
|
||||
if (isset($this->retailcrm_settings[$shipping['method_id']])) {
|
||||
$shipping_method = $shipping['method_id'];
|
||||
} elseif (isset($this->retailcrm_settings[$shipping_code[0]])) {
|
||||
$shipping_method = $shipping_code[0];
|
||||
} else {
|
||||
$shipping_method = $shipping['method_id'] . ':' . $shipping['instance_id'];
|
||||
}
|
||||
|
||||
if (!empty($shipping_method) && !empty($this->retailcrm_settings[$shipping_method])) {
|
||||
$orderData['delivery']['code'] = $this->retailcrm_settings[$shipping_method];
|
||||
$service = retailcrm_get_delivery_service($shipping['method_id'], $shipping['instance_id']);
|
||||
|
||||
if ($service) {
|
||||
$orderData['delivery']['service'] = [
|
||||
'name' => $service['title'],
|
||||
'code' => $service['instance_id'],
|
||||
'active' => true
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($shipping['total'])) {
|
||||
$orderData['delivery']['netCost'] = $shipping['total'];
|
||||
$orderData['delivery']['cost'] = isset($shipping['total_tax'])
|
||||
? $shipping['total'] + $shipping['total_tax']
|
||||
: $shipping['total'];
|
||||
|
||||
if (wc_tax_enabled()) {
|
||||
$shippingTaxClass = get_option('woocommerce_shipping_tax_class');
|
||||
|
||||
$rate = $shippingTaxClass == 'inherit'
|
||||
? getOrderItemRate($order)
|
||||
: getShippingRate();
|
||||
|
||||
if ($rate !== null) {
|
||||
$orderData['delivery']['vatRate'] = $rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$orderData['delivery']['address'] = $this->order_address->build($order)->getData();
|
||||
|
||||
$orderItems = [];
|
||||
$crmItems = [];
|
||||
$wcItems = $order->get_items();
|
||||
|
||||
if ($this->loyalty && $update) {
|
||||
$result = $this->loyalty->getCrmItemsInfo($order->get_id());
|
||||
|
||||
if ($result !== []) {
|
||||
$crmItems = $result['items'];
|
||||
|
||||
if (
|
||||
$statusTrash
|
||||
|| (
|
||||
$result['discountType'] !== null
|
||||
&& in_array($order->get_status(), ['cancelled', 'refunded'])
|
||||
)
|
||||
) {
|
||||
$this->cancelLoyalty = true;
|
||||
$this->order_item->cancelLoyalty = true;
|
||||
} else {
|
||||
$this->cancelLoyalty = $this->order_item->isCancelLoyalty($wcItems, $crmItems);
|
||||
}
|
||||
|
||||
$this->loyaltyDiscountType = $result['discountType'];
|
||||
}
|
||||
}
|
||||
|
||||
/** @var WC_Order_Item_Product $item */
|
||||
foreach ($wcItems as $id => $item) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Process WC_Order_Item_Product ' . $id,
|
||||
['wc_order_item_product' => WC_Retailcrm_Logger::formatWcObject($item)]
|
||||
);
|
||||
$crmItem = $crmItems[$id] ?? null;
|
||||
$orderItems[] = $this->order_item->build($item, $crmItem)->getData();
|
||||
|
||||
$this->order_item->resetData($this->cancelLoyalty);
|
||||
}
|
||||
|
||||
unset($crmItems, $crmItem);
|
||||
|
||||
$orderData['items'] = $orderItems;
|
||||
$orderData['discountManualAmount'] = 0;
|
||||
$orderData['discountManualPercent'] = 0;
|
||||
|
||||
if (!$update && $order->get_total() > 0) {
|
||||
$this->order_payment->isNew = true;
|
||||
$orderData['payments'][] = $this->order_payment->build($order)->getData();
|
||||
}
|
||||
|
||||
if (!empty($this->customFields)) {
|
||||
foreach ($this->customFields as $metaKey => $customKey) {
|
||||
$metaValue = $order->get_meta($metaKey);
|
||||
|
||||
if (empty($metaValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($customKey, 'default-crm-field') !== false) {
|
||||
$crmField = explode('#', $customKey);
|
||||
|
||||
if (count($crmField) === 2 && isset($crmField[1])) {
|
||||
$orderData[$crmField[1]] = $metaValue;
|
||||
} elseif (isset($crmField[1], $crmField[2], $crmField[3])) {
|
||||
// For order delivery
|
||||
$orderData[$crmField[1]][$crmField[2]][$crmField[3]] = $metaValue;
|
||||
}
|
||||
} else {
|
||||
$orderData['customFields'][$customKey] = $metaValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$couponCustomField = $this->retailcrm_settings['woo_coupon_apply_field'];
|
||||
|
||||
if ($couponCustomField !== 'not-upload') {
|
||||
$codeCoupons = [];
|
||||
|
||||
foreach ($order->get_coupons() as $coupon) {
|
||||
if (!empty($coupon->get_code())) {
|
||||
$codeCoupons[] = $coupon->get_code();
|
||||
}
|
||||
}
|
||||
|
||||
$orderData['customFields'][$couponCustomField] = implode('; ', $codeCoupons);
|
||||
}
|
||||
|
||||
$this->order = WC_Retailcrm_Plugin::clearArray($orderData);
|
||||
$this->processOrderCustomerInfo($order, $update);
|
||||
|
||||
$this->order = apply_filters(
|
||||
'retailcrm_process_order',
|
||||
WC_Retailcrm_Plugin::clearArray($this->order),
|
||||
$order
|
||||
);
|
||||
}
|
||||
|
||||
public function processOrderForUpload($orderIds)
|
||||
{
|
||||
$ordersForUpload = [];
|
||||
$errorOrders = [];
|
||||
|
||||
foreach ($orderIds as $orderId) {
|
||||
try {
|
||||
$this->order = [];
|
||||
$this->processOrder(wc_get_order($orderId));
|
||||
|
||||
if ($this->order === []) {
|
||||
throw new \RuntimeException(sprintf('Order %s is not uploaded', $orderId));
|
||||
}
|
||||
|
||||
$ordersForUpload[] = $this->order;
|
||||
} catch (Throwable $exception) {
|
||||
$errorOrders[$orderId] = sprintf(
|
||||
'Exception for Order [%s]: %s. Trace: %s',
|
||||
$orderId, $exception->getMessage(), $exception->getTraceAsString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return [$ordersForUpload, $errorOrders];
|
||||
}
|
||||
|
||||
/**
|
||||
* Send payment in CRM
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @param boolean $update
|
||||
* @param mixed $externalId
|
||||
*
|
||||
* @return array $payment
|
||||
*/
|
||||
protected function sendPayment($order, $update = false, $externalId = false)
|
||||
{
|
||||
$this->order_payment->isNew = !$update;
|
||||
|
||||
$payment = $this->order_payment->build($order, $externalId)->getData();
|
||||
$integrationPayments = get_option('retailcrm_integration_payments');
|
||||
|
||||
if (is_array($integrationPayments)) {
|
||||
$integrationPayments = array_flip($integrationPayments);
|
||||
}
|
||||
|
||||
if ($update === true && isset($integrationPayments[$payment['type']])) {
|
||||
return $payment;
|
||||
}
|
||||
|
||||
if ($update === false) {
|
||||
$this->retailcrm->ordersPaymentCreate($payment);
|
||||
} else {
|
||||
$this->retailcrm->ordersPaymentEdit($payment);
|
||||
}
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
/**
|
||||
* ordersGet wrapper with cache (in order to minimize request count).
|
||||
*
|
||||
* @param int|string $orderId
|
||||
* @param bool $cached
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCrmOrder($orderId, $cached = true)
|
||||
{
|
||||
if ($cached && isset($this->ordersGetRequestCache[$orderId])) {
|
||||
return (array) $this->ordersGetRequestCache[$orderId];
|
||||
}
|
||||
|
||||
$crmOrder = [];
|
||||
$response = $this->retailcrm->ordersGet($orderId);
|
||||
|
||||
if (!empty($response) && $response->isSuccessful() && isset($response['order'])) {
|
||||
$crmOrder = (array) $response['order'];
|
||||
$this->ordersGetRequestCache[$orderId] = $crmOrder;
|
||||
}
|
||||
|
||||
return $crmOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOrder()
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPayment()
|
||||
{
|
||||
return $this->payment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if provided order is for corporate customer
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isCorporateOrder($order)
|
||||
{
|
||||
$billingCompany = $order->get_billing_company();
|
||||
|
||||
return !empty($billingCompany);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if passed crm order is corporate
|
||||
*
|
||||
* @param array|\ArrayAccess $order
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isCorporateCrmOrder($order)
|
||||
{
|
||||
return (is_array($order) || $order instanceof ArrayAccess)
|
||||
&& isset($order['customer'])
|
||||
&& isset($order['customer']['type'])
|
||||
&& $order['customer']['type'] == 'customer_corporate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if customer in order was changed. `true` will be returned if one of these four conditions is met:
|
||||
*
|
||||
* 1. If CMS order is corporate and retailCRM order is not corporate or vice versa, then customer obviously
|
||||
* needs to be updated in retailCRM.
|
||||
* 2. If billing company from CMS order is not the same as the one in the retailCRM order,
|
||||
* then company needs to be updated.
|
||||
* 3. If contact person or individual externalId is different from customer ID in the CMS order, then
|
||||
* contact person or customer in retailCRM should be updated (even if customer id in the order is not set).
|
||||
* 4. If contact person or individual email is not the same as the CMS order billing email, then
|
||||
* contact person or customer in retailCRM should be updated.
|
||||
*
|
||||
* @param \WC_Order $wcOrder
|
||||
* @param array|\ArrayAccess $crmOrder
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isOrderCustomerWasChanged($wcOrder, $crmOrder)
|
||||
{
|
||||
if (!isset($crmOrder['customer'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$customerWasChanged = self::isCorporateOrder($wcOrder) != self::isCorporateCrmOrder($crmOrder);
|
||||
$synchronizableUserData = (self::isCorporateCrmOrder($crmOrder) && isset($crmOrder['contact']))
|
||||
? $crmOrder['contact'] : $crmOrder['customer'];
|
||||
|
||||
if (!$customerWasChanged) {
|
||||
if (self::isCorporateCrmOrder($crmOrder)) {
|
||||
$currentCrmCompany = isset($crmOrder['company']) ? $crmOrder['company']['name'] : '';
|
||||
|
||||
if (!empty($currentCrmCompany) && $currentCrmCompany != $wcOrder->get_billing_company()) {
|
||||
$customerWasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
isset($synchronizableUserData['externalId'])
|
||||
&& $synchronizableUserData['externalId'] != $wcOrder->get_customer_id()
|
||||
) {
|
||||
$customerWasChanged = true;
|
||||
} elseif (
|
||||
isset($synchronizableUserData['email'])
|
||||
&& $synchronizableUserData['email'] != $wcOrder->get_billing_email()
|
||||
) {
|
||||
$customerWasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $customerWasChanged;
|
||||
}
|
||||
}
|
||||
endif;
|
206
src/include/class-wc-retailcrm-plugin.php
Normal file
206
src/include/class-wc-retailcrm-plugin.php
Normal file
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Plugin - Internal plugin settings.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Plugin
|
||||
{
|
||||
|
||||
public $file;
|
||||
public static $history_run = false;
|
||||
private static $instance = null;
|
||||
|
||||
const MARKETPLACE_LOGO = 'https://s3.eu-central-1.amazonaws.com/retailcrm-billing/images/5b69ce4bda663-woocommercesvg2.svg';
|
||||
const INTEGRATION_CODE = 'woocommerce';
|
||||
|
||||
public static function getInstance($file)
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self($file);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $file
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function __construct($file)
|
||||
{
|
||||
$this->file = $file;
|
||||
|
||||
add_filter('cron_schedules', [$this, 'filter_cron_schedules'], 10, 1);
|
||||
}
|
||||
|
||||
public function filter_cron_schedules($schedules)
|
||||
{
|
||||
return array_merge(
|
||||
$schedules,
|
||||
[
|
||||
'five_minutes' => [
|
||||
'interval' => 300, // seconds
|
||||
'display' => __('Every 5 minutes')
|
||||
],
|
||||
'three_hours' => [
|
||||
'interval' => 10800, // seconds
|
||||
'display' => __('Every 3 hours')
|
||||
],
|
||||
'fiveteen_minutes' => [
|
||||
'interval' => 900, // seconds
|
||||
'display' => __('Every 15 minutes')
|
||||
],
|
||||
'four_hours' => [
|
||||
'interval' => 14400, //seconds
|
||||
'display' => __('Every 4 hours')
|
||||
]
|
||||
],
|
||||
apply_filters('retailcrm_add_cron_interval', $schedules)
|
||||
);
|
||||
}
|
||||
|
||||
public function register_activation_hook()
|
||||
{
|
||||
register_activation_hook($this->file, array($this, 'activate'));
|
||||
}
|
||||
|
||||
public function register_deactivation_hook()
|
||||
{
|
||||
register_deactivation_hook($this->file, array($this, 'deactivate'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function activate()
|
||||
{
|
||||
if (!class_exists('WC_Integration')) {
|
||||
add_action('admin_notices', [new WC_Integration_Retailcrm(), 'woocommerce_missing_notice']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Logger')) {
|
||||
require_once(WC_Integration_Retailcrm::checkCustomFile('include/components/class-wc-retailcrm-logger.php'));
|
||||
}
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Icml')) {
|
||||
require_once(WC_Integration_Retailcrm::checkCustomFile('include/class-wc-retailcrm-icml.php'));
|
||||
}
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Icml_Writer')) {
|
||||
require_once(WC_Integration_Retailcrm::checkCustomFile('include/icml/class-wc-retailcrm-icml-writer.php'));
|
||||
}
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Base')) {
|
||||
require_once(WC_Integration_Retailcrm::checkCustomFile('include/class-wc-retailcrm-base.php'));
|
||||
}
|
||||
|
||||
$retailcrm_icml = new WC_Retailcrm_Icml();
|
||||
$retailcrm_icml->generate();
|
||||
}
|
||||
|
||||
public function deactivate()
|
||||
{
|
||||
do_action('retailcrm_deactivate');
|
||||
|
||||
if (wp_next_scheduled('retailcrm_icml')) {
|
||||
wp_clear_scheduled_hook('retailcrm_icml');
|
||||
}
|
||||
|
||||
if (wp_next_scheduled('retailcrm_history')) {
|
||||
wp_clear_scheduled_hook('retailcrm_history');
|
||||
}
|
||||
|
||||
if (wp_next_scheduled('retailcrm_inventories')) {
|
||||
wp_clear_scheduled_hook('retailcrm_inventories');
|
||||
}
|
||||
|
||||
if (wp_next_scheduled('retailcrm_loyalty_upload_price')) {
|
||||
wp_clear_scheduled_hook('retailcrm_loyalty_upload_price');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit configuration in CRM
|
||||
*
|
||||
* @param WC_Retailcrm_Proxy|WC_Retailcrm_Client_V5 $api_client
|
||||
* @param string $client_id
|
||||
* @param bool $active
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function integration_module($api_client, $client_id, $active = true)
|
||||
{
|
||||
if (!$api_client instanceof WC_Retailcrm_Proxy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$configuration = array(
|
||||
'name' => 'WooCommerce',
|
||||
'logo' => self::MARKETPLACE_LOGO,
|
||||
'code' => self::INTEGRATION_CODE . '-' . $client_id,
|
||||
'active' => $active,
|
||||
);
|
||||
|
||||
$configuration['integrationCode'] = self::INTEGRATION_CODE;
|
||||
$configuration['baseUrl'] = get_site_url();
|
||||
$configuration['clientId'] = $client_id;
|
||||
$configuration['accountUrl'] = get_site_url();
|
||||
|
||||
$response = $api_client->integrationModulesEdit($configuration);
|
||||
|
||||
return !empty($response) && $response->isSuccessful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset empty fields
|
||||
*
|
||||
* @param array $arr input array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function clearArray(array $arr)
|
||||
{
|
||||
if (!is_array($arr)) {
|
||||
return $arr;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($arr as $index => $node) {
|
||||
$result[$index] = (is_array($node))
|
||||
? self::clearArray($node)
|
||||
: $node;
|
||||
|
||||
if (
|
||||
$result[$index] === ''
|
||||
|| $result[$index] === null
|
||||
|| (is_array($result[$index]) && count($result[$index]) < 1)
|
||||
) {
|
||||
unset($result[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check running history
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function history_running()
|
||||
{
|
||||
return self::$history_run;
|
||||
}
|
||||
}
|
238
src/include/class-wc-retailcrm-upload-discount-price.php
Normal file
238
src/include/class-wc-retailcrm-upload-discount-price.php
Normal file
|
@ -0,0 +1,238 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Upload_Discount_Price')):
|
||||
|
||||
class WC_Retailcrm_Upload_Discount_Price
|
||||
{
|
||||
const DISCOUNT_TYPE_PRICE = 'woo-promotion-lp';
|
||||
|
||||
protected $activeLoyalty = false;
|
||||
|
||||
protected $settings;
|
||||
|
||||
protected $site;
|
||||
|
||||
/** @var bool|WC_Retailcrm_Proxy|WC_Retailcrm_Client_V5 */
|
||||
protected $apiClient;
|
||||
|
||||
public function __construct($aplClient = false)
|
||||
{
|
||||
$this->settings = get_option(WC_Retailcrm_Base::$option_key);
|
||||
$this->apiClient = $aplClient;
|
||||
|
||||
if (isset($this->settings['loyalty']) && $this->settings['loyalty'] === WC_Retailcrm_Base::YES) {
|
||||
$this->activeLoyalty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function upload()
|
||||
{
|
||||
if (!$this->activeLoyalty) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $this->uploadSettings();
|
||||
|
||||
if ($error !== '') {
|
||||
WC_Retailcrm_Logger::error(__METHOD__, $error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$productStatuses = $this->getProductStatuses();
|
||||
|
||||
if (!$productStatuses) {
|
||||
$productStatuses = ['publish'];
|
||||
}
|
||||
|
||||
$page = 1;
|
||||
$requestData = [];
|
||||
|
||||
do {
|
||||
$products = wc_get_products(
|
||||
[
|
||||
'limit' => 1000,
|
||||
'status' => $productStatuses,
|
||||
'page' => $page,
|
||||
'paginate' => true
|
||||
]
|
||||
);
|
||||
|
||||
/** WP version >= 6 */
|
||||
if (function_exists('wp_cache_flush_runtime')) {
|
||||
wp_cache_flush_runtime();
|
||||
} else {
|
||||
wp_cache_flush();
|
||||
}
|
||||
|
||||
if (empty($products)) {
|
||||
WC_Retailcrm_Logger::error(__METHOD__, 'Can`t get products!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($products->products as $offer) {
|
||||
$type = $offer->get_type();
|
||||
|
||||
if (strpos($type, 'variable') !== false || strpos($type, 'variation') !== false) {
|
||||
foreach ($offer->get_children() as $childId) {
|
||||
$childProduct = wc_get_product($childId);
|
||||
|
||||
if (!$childProduct) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sendOffer = $this->getOfferData($childProduct);
|
||||
|
||||
if ($sendOffer !== []) {
|
||||
$requestData[] = $sendOffer;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sendOffer = $this->getOfferData($offer);
|
||||
|
||||
if ($sendOffer !== []) {
|
||||
$requestData[] = $sendOffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$chunks = array_chunk($requestData, 250);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$this->apiClient->storePricesUpload($chunk, $this->site);
|
||||
time_nanosleep(0, 200000000);
|
||||
}
|
||||
|
||||
unset($chunks);
|
||||
} catch (\Throwable $exception) {
|
||||
WC_Retailcrm_Logger::exception(__METHOD__, $exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
++$page;
|
||||
} while ($page <= $products->max_num_pages);
|
||||
}
|
||||
|
||||
private function getOfferData(WC_Product $product)
|
||||
{
|
||||
$currentPrice = wc_get_price_including_tax($product);
|
||||
$defaultPrice = wc_get_price_including_tax($product, ["price" => $product->get_regular_price()]);
|
||||
|
||||
if ($currentPrice === $defaultPrice) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'externalId' => $product->get_id(),
|
||||
'site' => $this->site,
|
||||
'prices' => [
|
||||
[
|
||||
'code' => self::DISCOUNT_TYPE_PRICE,
|
||||
'price' => $currentPrice
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function getProductStatuses()
|
||||
{
|
||||
$statuses = [];
|
||||
|
||||
foreach (get_post_statuses() as $key => $value) {
|
||||
if (isset($this->settings['p_' . $key]) && $this->settings['p_' . $key] == WC_Retailcrm_Base::YES) {
|
||||
$statuses[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
private function uploadSettings()
|
||||
{
|
||||
if (!$this->apiClient instanceof WC_Retailcrm_Proxy
|
||||
&& !$this->apiClient instanceof WC_Retailcrm_Client_V5
|
||||
) {
|
||||
return 'API client has not been initialized';
|
||||
}
|
||||
|
||||
$this->site = $this->apiClient->getSingleSiteForKey();
|
||||
|
||||
if (empty($this->site)) {
|
||||
return 'Error with CRM credentials: need an valid apiKey assigned to one certain site';
|
||||
}
|
||||
|
||||
$response = $this->apiClient->getPriceTypes();
|
||||
|
||||
if (
|
||||
!$response instanceof WC_Retailcrm_Response
|
||||
|| !$response->offsetExists('priceTypes')
|
||||
|| empty($response['priceTypes'])
|
||||
) {
|
||||
return 'Error getting price types';
|
||||
}
|
||||
|
||||
$defaultPrice = null;
|
||||
$discountPriceType = null;
|
||||
|
||||
foreach ($response['priceTypes'] as $priceType) {
|
||||
if ($priceType['default'] === true) {
|
||||
$defaultPrice = $priceType;
|
||||
}
|
||||
|
||||
if ($priceType['code'] === self::DISCOUNT_TYPE_PRICE) {
|
||||
$discountPriceType = $priceType;
|
||||
}
|
||||
}
|
||||
|
||||
if ($discountPriceType === null) {
|
||||
$discountPriceType = [
|
||||
'code' => self::DISCOUNT_TYPE_PRICE,
|
||||
'name' => __('Woocommerce promotional price', 'retailcrm'),
|
||||
'active' => true,
|
||||
'description' => __('Promotional price type for Woocommerce store, generated automatically.
|
||||
Necessary for correct synchronization work when loyalty program is enabled
|
||||
(Do not delete. Do not deactivate)', 'retailcrm'),
|
||||
'ordering' => 999,
|
||||
'promo' => true
|
||||
];
|
||||
|
||||
if (isset($defaultPrice['geo'])) {
|
||||
$discountPriceType['geo'] = $defaultPrice['geo'];
|
||||
}
|
||||
|
||||
if (isset($defaultPrice['groups'])) {
|
||||
$discountPriceType['groups'] = $defaultPrice['groups'];
|
||||
}
|
||||
|
||||
if (isset($defaultPrice['currency'])) {
|
||||
$discountPriceType['currency'] = $defaultPrice['currency'];
|
||||
}
|
||||
|
||||
if (isset($defaultPrice['filterExpression'])) {
|
||||
$discountPriceType['filterExpression'] = $defaultPrice['filterExpression'];
|
||||
}
|
||||
|
||||
$response = $this->apiClient->editPriceType($discountPriceType);
|
||||
|
||||
if (!$response instanceof WC_Retailcrm_Response || !$response['success']) {
|
||||
return 'Error creating price type';
|
||||
}
|
||||
} elseif ($discountPriceType['active'] === false || $discountPriceType['promo'] === false) {
|
||||
$discountPriceType['active'] = true;
|
||||
$discountPriceType['promo'] = true;
|
||||
|
||||
$response = $this->apiClient->editPriceType($discountPriceType);
|
||||
|
||||
if (!$response instanceof WC_Retailcrm_Response || !$response['success']) {
|
||||
return 'Error activate price type';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
305
src/include/class-wc-retailcrm-uploader.php
Normal file
305
src/include/class-wc-retailcrm-uploader.php
Normal file
|
@ -0,0 +1,305 @@
|
|||
<?php
|
||||
|
||||
if (class_exists('WC_Retailcrm_Uploader') === false) {
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Uploader - Allows upload archival orders/customers in CRM.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Uploader
|
||||
{
|
||||
const RETAILCRM_COUNT_OBJECT_UPLOAD = 50;
|
||||
|
||||
/**
|
||||
* Api client RetailCRM
|
||||
*
|
||||
* @var WC_Retailcrm_Client_V5
|
||||
*/
|
||||
private $retailcrm;
|
||||
|
||||
/**
|
||||
* Orders RetailCRM
|
||||
*
|
||||
* @var WC_Retailcrm_Orders
|
||||
*/
|
||||
private $orders;
|
||||
|
||||
/**
|
||||
* Customers RetailCRM
|
||||
*
|
||||
* @var WC_Retailcrm_Customers
|
||||
*/
|
||||
private $customers;
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Uploader constructor.
|
||||
*
|
||||
* @param WC_Retailcrm_Client_V5 $retailcrm Api client RetailCRM.
|
||||
* @param WC_Retailcrm_Orders $orders Object order RetailCRM.
|
||||
* @param WC_Retailcrm_Customers $customers Object customer RetailCRM.
|
||||
*/
|
||||
public function __construct($retailcrm, $orders, $customers)
|
||||
{
|
||||
$this->retailcrm = $retailcrm;
|
||||
$this->orders = $orders;
|
||||
$this->customers = $customers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads selected order in CRM
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception Invalid argument exception.
|
||||
*/
|
||||
public function uploadSelectedOrders()
|
||||
{
|
||||
$ids = $_GET['order_ids_retailcrm'];
|
||||
|
||||
WC_Retailcrm_Logger::info(__METHOD__, 'Selected order IDs: ' . json_encode($ids));
|
||||
|
||||
if (!empty($ids)) {
|
||||
preg_match_all('/\d+/', $ids, $matches);
|
||||
|
||||
if (!empty($matches[0])) {
|
||||
$this->uploadArchiveOrders(null, $matches[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads archive order in CRM
|
||||
*
|
||||
* @param int|null $page Number page uploads.
|
||||
* @param array $ids Ids orders upload.
|
||||
*
|
||||
* @return void|null
|
||||
* @throws Exception Invalid argument exception.
|
||||
*/
|
||||
public function uploadArchiveOrders(?int $page, array $ids = [])
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$orderIds = [];
|
||||
|
||||
if (null !== $page) {
|
||||
$orderIds = $this->getCmsOrders($page);
|
||||
} elseif ([] !== $ids) {
|
||||
$orderIds = $ids;
|
||||
}
|
||||
|
||||
if ($orderIds === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WC_Retailcrm_Logger::info(__METHOD__, 'Archive order IDs: ' . implode(', ', $ids));
|
||||
|
||||
[$ordersForUpload, $uploadErrors] = $this->orders->processOrderForUpload($orderIds);
|
||||
|
||||
try {
|
||||
$response = $this->retailcrm->ordersUpload($ordersForUpload);
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
'Failure to upload orders: %s. Status code: %s',
|
||||
$response->getErrorString(),
|
||||
$response->getStatusCode()
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
WC_Retailcrm_Logger::error(
|
||||
__METHOD__,
|
||||
sprintf("Error while uploading orders: %s", $exception->getMessage())
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** WP version >= 6 */
|
||||
if (function_exists('wp_cache_flush_runtime')) {
|
||||
wp_cache_flush_runtime();
|
||||
} else {
|
||||
wp_cache_flush();
|
||||
}
|
||||
|
||||
$this->logOrdersUploadErrors($uploadErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads archive customer in CRM
|
||||
*
|
||||
* @param integer $page Number page uploads.
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception Invalid argument exception.
|
||||
*/
|
||||
public function uploadArchiveCustomers($page)
|
||||
{
|
||||
if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$users = $this->getCmsUsers($page);
|
||||
|
||||
if ($users !== []) {
|
||||
$dataCustomers = [];
|
||||
|
||||
foreach ($users as $user) {
|
||||
if ($this->customers->isCustomer($user) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$customer = new WC_Customer($user->ID);
|
||||
$this->customers->processCustomerForUpload($customer);
|
||||
$dataCustomers[] = $this->customers->getCustomer();
|
||||
}
|
||||
|
||||
$this->retailcrm->customersUpload($dataCustomers);
|
||||
}
|
||||
|
||||
return $dataCustomers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return orders ids
|
||||
*
|
||||
* @param integer $page Number page uploads.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getCmsOrders($page)
|
||||
{
|
||||
return wc_get_orders(
|
||||
[
|
||||
'type' => wc_get_order_types('view-orders'),
|
||||
'limit' => self::RETAILCRM_COUNT_OBJECT_UPLOAD,
|
||||
'status' => array_keys(wc_get_order_statuses()),
|
||||
'offset' => self::RETAILCRM_COUNT_OBJECT_UPLOAD * $page,
|
||||
'return' => 'ids',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return count orders
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getCountOrders()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
if (useHpos()) {
|
||||
// Use {$wpdb->prefix}, because wp_wc_orders not standard WP table
|
||||
$result = $wpdb->get_results("SELECT COUNT(ID) as `count` FROM {$wpdb->prefix}wc_orders");
|
||||
} else {
|
||||
$result = $wpdb->get_results("SELECT COUNT(ID) as `count` FROM $wpdb->posts WHERE post_type = 'shop_order'");
|
||||
}
|
||||
|
||||
return $result[0]->count ? (int) $result[0]->count : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return users ids
|
||||
*
|
||||
* @param integer $page Number page uploads.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getCmsUsers(int $page)
|
||||
{
|
||||
return get_users(
|
||||
[
|
||||
'number' => self::RETAILCRM_COUNT_OBJECT_UPLOAD,
|
||||
'offset' => self::RETAILCRM_COUNT_OBJECT_UPLOAD * $page,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return count users
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getCountUsers()
|
||||
{
|
||||
$userCount = count_users();
|
||||
|
||||
return $userCount['total_users'] ? (int) $userCount['total_users'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array keys must be orders ID's in WooCommerce, values must be strings (error messages).
|
||||
*
|
||||
* @param array $errors Id order - key and message error - value.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function logOrdersUploadErrors($errors)
|
||||
{
|
||||
if ($errors === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($errors as $orderId => $error) {
|
||||
WC_Retailcrm_Logger::error(
|
||||
__METHOD__,
|
||||
sprintf("Error while uploading [%d] => %s", $orderId, $error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadConsole($entity, $page = 0)
|
||||
{
|
||||
$ordersPages = (int) ceil($this->getCountOrders() / 50);
|
||||
$customerPages = (int) ceil($this->getCountUsers() / 50);
|
||||
|
||||
try {
|
||||
switch ($entity) {
|
||||
case 'orders':
|
||||
$this->archiveUpload('orders', $page, $ordersPages);
|
||||
break;
|
||||
case 'customers':
|
||||
$this->archiveUpload('customers', $page, $customerPages);
|
||||
break;
|
||||
case 'full_upload':
|
||||
$this->archiveUpload('customers', 0, $customerPages);
|
||||
$this->archiveUpload('orders', 0, $ordersPages);
|
||||
break;
|
||||
default:
|
||||
echo 'Unknown entity: ' . $entity;
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
echo $exception->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function archiveUpload($entity, $page, $totalPages)
|
||||
{
|
||||
echo $entity . ' uploading started' . PHP_EOL;
|
||||
|
||||
do {
|
||||
if ($entity === 'orders') {
|
||||
$this->uploadArchiveOrders($page);
|
||||
} elseif ($entity === 'customers') {
|
||||
$this->uploadArchiveCustomers($page);
|
||||
}
|
||||
|
||||
echo $page . ' page uploaded' . PHP_EOL;
|
||||
|
||||
$page++;
|
||||
} while ($page <= $totalPages);
|
||||
}
|
||||
}
|
||||
}
|
311
src/include/components/class-wc-retailcrm-customer-switcher.php
Normal file
311
src/include/components/class-wc-retailcrm-customer-switcher.php
Normal file
|
@ -0,0 +1,311 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Customer_Switcher - This component provides builder-like interface in order to make it easier to
|
||||
* change customer & customer data in the order via retailCRM history.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Customer_Switcher implements WC_Retailcrm_Builder_Interface
|
||||
{
|
||||
/**
|
||||
* @var \WC_Retailcrm_Customer_Switcher_State $data
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @var \WC_Retailcrm_Customer_Switcher_Result|null $result
|
||||
*/
|
||||
private $result;
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Customer_Data_Replacer constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* In fact, this will execute customer change in provided order.
|
||||
* This will not produce any new entities.
|
||||
*
|
||||
* @return $this|\WC_Retailcrm_Builder_Interface
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$this->data->validate();
|
||||
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Build Customer state',
|
||||
['customer_state' => $this->data]
|
||||
);
|
||||
|
||||
$newCustomer = $this->data->getNewCustomer();
|
||||
$newContact = $this->data->getNewContact();
|
||||
$newCompany = $this->data->getNewCompanyName();
|
||||
$companyAddress = $this->data->getCompanyAddress();
|
||||
|
||||
if (!empty($newCustomer)) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Changing to individual customer for order ' . $this->data->getWcOrder()->get_id()
|
||||
);
|
||||
$this->processChangeToRegular($this->data->getWcOrder(), $newCustomer, false);
|
||||
$this->data->getWcOrder()->set_billing_company('');
|
||||
} else {
|
||||
if (!empty($newContact)) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Changing to contact person customer for order ' . $this->data->getWcOrder()->get_id()
|
||||
);
|
||||
$this->processChangeToRegular($this->data->getWcOrder(), $newContact, true);
|
||||
}
|
||||
|
||||
if (!empty($newCompany)) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
sprintf(
|
||||
'Replacing old order id=`%d` company `%s` with new company `%s`',
|
||||
$this->data->getWcOrder()->get_id(),
|
||||
$this->data->getWcOrder()->get_billing_company(),
|
||||
$newCompany
|
||||
)
|
||||
);
|
||||
$this->processCompanyChange();
|
||||
}
|
||||
|
||||
if (!empty($companyAddress)) {
|
||||
$this->processCompanyAddress();
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change order customer to regular one
|
||||
*
|
||||
* @param \WC_Order $wcOrder
|
||||
* @param array $newCustomer
|
||||
* @param bool $isContact
|
||||
*
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function processChangeToRegular($wcOrder, $newCustomer, $isContact)
|
||||
{
|
||||
$wcCustomer = null;
|
||||
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Switching customer in order ' . $wcOrder->get_id(),
|
||||
['crm_customer' => $newCustomer]
|
||||
);
|
||||
|
||||
if (isset($newCustomer['externalId'])) {
|
||||
$wcCustomer = new WC_Customer($newCustomer['externalId']);
|
||||
|
||||
if (!empty($wcCustomer)) {
|
||||
$wcOrder->set_customer_id($wcCustomer->get_id());
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
sprintf('Set customer to %s in order %s', $wcCustomer->get_id(), $wcOrder->get_id())
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$wcOrder->set_customer_id(0);
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Set customer to 0 (guest) in order ' . $wcOrder->get_id()
|
||||
);
|
||||
}
|
||||
|
||||
$fields = array(
|
||||
'billing_first_name' => self::arrayValue($newCustomer, 'firstName'),
|
||||
'billing_last_name' => self::arrayValue($newCustomer, 'lastName'),
|
||||
'billing_email' => self::arrayValue($newCustomer, 'email')
|
||||
);
|
||||
|
||||
foreach ($fields as $field => $value) {
|
||||
$wcOrder->{'set_' . $field}($value);
|
||||
}
|
||||
|
||||
$address = self::arrayValue($newCustomer, 'address', array());
|
||||
|
||||
if ($isContact) {
|
||||
self::setShippingAddressToOrder($wcOrder, $address);
|
||||
} else {
|
||||
self::setBillingAddressToOrder($wcOrder, $address);
|
||||
self::setShippingAddressToOrder($wcOrder, $address);
|
||||
}
|
||||
|
||||
$wcOrder->set_billing_phone(self::singleCustomerPhone($newCustomer));
|
||||
|
||||
$this->result = new WC_Retailcrm_Customer_Switcher_Result($wcCustomer, $wcOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process company address.
|
||||
*
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
protected function processCompanyAddress()
|
||||
{
|
||||
$wcOrder = $this->data->getWcOrder();
|
||||
$companyAddress = $this->data->getCompanyAddress();
|
||||
|
||||
if (!empty($companyAddress)) {
|
||||
self::setBillingAddressToOrder($wcOrder, $companyAddress);
|
||||
}
|
||||
|
||||
if (empty($this->result)) {
|
||||
$this->result = new WC_Retailcrm_Customer_Switcher_Result(null, $wcOrder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will update company field in order and create result if it's not set (happens when only company was changed).
|
||||
*
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
public function processCompanyChange()
|
||||
{
|
||||
$this->data->getWcOrder()->set_billing_company($this->data->getNewCompanyName());
|
||||
|
||||
if (empty($this->result)) {
|
||||
$this->result = new WC_Retailcrm_Customer_Switcher_Result(null, $this->data->getWcOrder());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\WC_Retailcrm_Builder_Interface
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->data = new WC_Retailcrm_Customer_Switcher_State();
|
||||
$this->result = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set initial state into component
|
||||
*
|
||||
* @param \WC_Retailcrm_Customer_Switcher_State $data
|
||||
*
|
||||
* @return $this|\WC_Retailcrm_Builder_Interface
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
if (!($data instanceof WC_Retailcrm_Customer_Switcher_State)) {
|
||||
throw new \InvalidArgumentException('Invalid data type');
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WC_Retailcrm_Customer_Switcher_Result|null
|
||||
*/
|
||||
public function getResult()
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WC_Retailcrm_Customer_Switcher_State
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets billing address properties in order
|
||||
*
|
||||
* @param \WC_Order $wcOrder
|
||||
* @param array $address
|
||||
*
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
private static function setBillingAddressToOrder($wcOrder, $address)
|
||||
{
|
||||
$wcOrder->set_billing_state(self::arrayValue($address, 'region', ''));
|
||||
$wcOrder->set_billing_postcode(self::arrayValue($address, 'index', ''));
|
||||
$wcOrder->set_billing_country(self::arrayValue($address, 'country', ''));
|
||||
$wcOrder->set_billing_city(self::arrayValue($address, 'city', ''));
|
||||
$wcOrder->set_billing_address_1(self::arrayValue($address, 'text', ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets shipping address properties in order
|
||||
*
|
||||
* @param \WC_Order $wcOrder
|
||||
* @param array $address
|
||||
*
|
||||
* @throws \WC_Data_Exception
|
||||
*/
|
||||
private static function setShippingAddressToOrder($wcOrder, $address)
|
||||
{
|
||||
$wcOrder->set_shipping_state(self::arrayValue($address, 'region', ''));
|
||||
$wcOrder->set_shipping_postcode(self::arrayValue($address, 'index', ''));
|
||||
$wcOrder->set_shipping_country(self::arrayValue($address, 'country', ''));
|
||||
$wcOrder->set_shipping_city(self::arrayValue($address, 'city', ''));
|
||||
$wcOrder->set_shipping_address_1(self::arrayValue($address, 'text', ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|\ArrayObject|\ArrayAccess $arr
|
||||
* @param string $key
|
||||
* @param string $def
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
private static function arrayValue($arr, $key, $def = '')
|
||||
{
|
||||
if (!is_array($arr) && !($arr instanceof ArrayObject) && !($arr instanceof ArrayAccess)) {
|
||||
return $def;
|
||||
}
|
||||
|
||||
if (!array_key_exists($key, $arr) && !empty($arr[$key])) {
|
||||
return $def;
|
||||
}
|
||||
|
||||
return isset($arr[$key]) ? $arr[$key] : $def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first phone from order data or null
|
||||
*
|
||||
* @param array $customerData
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private static function singleCustomerPhone($customerData)
|
||||
{
|
||||
if (!array_key_exists('phones', $customerData)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (empty($customerData['phones']) || !is_array($customerData['phones'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$phones = $customerData['phones'];
|
||||
$phone = reset($phones);
|
||||
|
||||
if (!isset($phone['number'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $phone['number'];
|
||||
}
|
||||
}
|
430
src/include/components/class-wc-retailcrm-history-assembler.php
Normal file
430
src/include/components/class-wc-retailcrm-history-assembler.php
Normal file
|
@ -0,0 +1,430 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_History_Assembler - Assembles history records into list which closely resembles
|
||||
* orders & customers list output from API.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_History_Assembler
|
||||
{
|
||||
/**
|
||||
* Assembles orders list from history data
|
||||
*
|
||||
* @param array $orderHistory
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function assemblyOrder($orderHistory)
|
||||
{
|
||||
$fields = self::getMappingValues();
|
||||
$orders = [];
|
||||
$orderHistory = self::filterHistory($orderHistory, 'order');
|
||||
|
||||
foreach ($orderHistory as $change) {
|
||||
$change['order'] = self::removeEmpty($change['order']);
|
||||
|
||||
if (isset($change['order']['items']) && $change['order']['items']) {
|
||||
$items = [];
|
||||
|
||||
foreach ($change['order']['items'] as $item) {
|
||||
if (isset($change['created'])) {
|
||||
$item['create'] = 1;
|
||||
}
|
||||
|
||||
$items[$item['id']] = $item;
|
||||
}
|
||||
$change['order']['items'] = $items;
|
||||
}
|
||||
|
||||
if (isset($change['order']['contragent']['contragentType']) && $change['order']['contragent']['contragentType']) {
|
||||
$change['order']['contragentType'] = $change['order']['contragent']['contragentType'];
|
||||
|
||||
unset($change['order']['contragent']);
|
||||
}
|
||||
|
||||
$orderMainInfo = WC_Retailcrm_Plugin::clearArray(
|
||||
[
|
||||
'id' => $change['order']['id'] ?? '',
|
||||
'externalId' => $change['order']['externalId'] ?? '',
|
||||
'managerId' => $change['order']['managerId'] ?? '',
|
||||
'site' => $change['order']['site'] ?? '',
|
||||
]
|
||||
);
|
||||
|
||||
if (!empty($orders) && isset($orders[$change['order']['id']])) {
|
||||
$orders[$change['order']['id']] = array_merge($orders[$change['order']['id']], $orderMainInfo);
|
||||
} else {
|
||||
$orders[$change['order']['id']] = !empty($change['created']) ? $change['order'] : $orderMainInfo;
|
||||
}
|
||||
|
||||
if ($change['field'] === 'status') {
|
||||
$orders[$change['order']['id']]['status'] = $change['order']['status'];
|
||||
}
|
||||
|
||||
if (isset($change['item']) && $change['item']) {
|
||||
if (isset($orders[$change['order']['id']]['items'][$change['item']['id']])) {
|
||||
$orders[$change['order']['id']]['items'][$change['item']['id']] = array_merge($orders[$change['order']['id']]['items'][$change['item']['id']], $change['item']);
|
||||
} else {
|
||||
$orders[$change['order']['id']]['items'][$change['item']['id']] = $change['item'];
|
||||
}
|
||||
|
||||
if ($change['oldValue'] === null && $change['field'] == 'order_product') {
|
||||
$orders[$change['order']['id']]['items'][$change['item']['id']]['create'] = true;
|
||||
}
|
||||
|
||||
if ($change['newValue'] === null && $change['field'] == 'order_product') {
|
||||
$orders[$change['order']['id']]['items'][$change['item']['id']]['delete'] = true;
|
||||
}
|
||||
|
||||
if (isset($fields['item'][$change['field']]) && $fields['item'][$change['field']]) {
|
||||
$orders[$change['order']['id']]['items'][$change['item']['id']][$fields['item'][$change['field']]] = $change['newValue'];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($change['payment']) && $change['field'] == 'payments') {
|
||||
if ($change['newValue'] !== null) {
|
||||
$orders[$change['order']['id']]['payments'][] = self::newValue($change['payment']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($change['field'] == 'payments.status' && $change['newValue'] !== null) {
|
||||
$orders[$change['order']['id']]['payments']['id']['status'] = self::newValue($change['newValue']);
|
||||
} else {
|
||||
if (isset($fields['delivery'][$change['field']]) && $fields['delivery'][$change['field']] == 'service') {
|
||||
$orders[$change['order']['id']]['delivery']['service']['code'] = self::newValue($change['newValue']);
|
||||
} elseif (isset($fields['delivery'][$change['field']]) && $fields['delivery'][$change['field']]) {
|
||||
$orders[$change['order']['id']]['delivery'][$fields['delivery'][$change['field']]] = self::newValue($change['newValue']);
|
||||
} elseif (isset($fields['orderAddress'][$change['field']]) && $fields['orderAddress'][$change['field']]) {
|
||||
$orders[$change['order']['id']]['delivery']['address'][$fields['orderAddress'][$change['field']]] = $change['newValue'];
|
||||
} elseif (isset($fields['integrationDelivery'][$change['field']]) && $fields['integrationDelivery'][$change['field']]) {
|
||||
$orders[$change['order']['id']]['delivery']['service'][$fields['integrationDelivery'][$change['field']]] = self::newValue($change['newValue']);
|
||||
} elseif (isset($fields['customerContragent'][$change['field']]) && $fields['customerContragent'][$change['field']]) {
|
||||
$orders[$change['order']['id']][$fields['customerContragent'][$change['field']]] = self::newValue($change['newValue']);
|
||||
} elseif (strripos($change['field'], 'custom_') !== false) {
|
||||
$orders[$change['order']['id']]['customFields'][str_replace('custom_', '', $change['field'])] = self::newValue($change['newValue']);
|
||||
} elseif (isset($fields['order'][$change['field']]) && $fields['order'][$change['field']]) {
|
||||
$orders[$change['order']['id']][$fields['order'][$change['field']]] = self::newValue($change['newValue']);
|
||||
}
|
||||
|
||||
if (isset($change['created'])) {
|
||||
$orders[$change['order']['id']]['create'] = 1;
|
||||
}
|
||||
|
||||
if (isset($change['deleted'])) {
|
||||
$orders[$change['order']['id']]['deleted'] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles customers list from history changes
|
||||
*
|
||||
* @param array $customerHistory
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function assemblyCustomer($customerHistory)
|
||||
{
|
||||
$customers = array();
|
||||
$fields = self::getMappingValues(array('customer'));
|
||||
$fieldsAddress = self::getMappingValues(array('customerAddress'));
|
||||
$customerHistory = self::filterHistory($customerHistory, 'customer');
|
||||
|
||||
foreach ($customerHistory as $change) {
|
||||
$change['customer'] = self::removeEmpty($change['customer']);
|
||||
|
||||
if (
|
||||
isset($change['deleted'])
|
||||
&& $change['deleted']
|
||||
&& isset($customers[$change['customer']['id']])
|
||||
) {
|
||||
$customers[$change['customer']['id']]['deleted'] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($change['field'] == 'id') {
|
||||
$customers[$change['customer']['id']] = $change['customer'];
|
||||
}
|
||||
|
||||
if (isset($customers[$change['customer']['id']])) {
|
||||
$customers[$change['customer']['id']] = array_merge($customers[$change['customer']['id']], $change['customer']);
|
||||
} else {
|
||||
$customers[$change['customer']['id']] = $change['customer'];
|
||||
}
|
||||
|
||||
if (
|
||||
isset($fields['customer'][$change['field']])
|
||||
&& $fields['customer'][$change['field']]
|
||||
) {
|
||||
$customers[
|
||||
$change['customer']['id']
|
||||
][
|
||||
$fields['customer'][$change['field']]
|
||||
] = self::newValue($change['newValue']);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($fieldsAddress['customerAddress'][$change['field']])
|
||||
&& $fieldsAddress['customerAddress'][$change['field']]
|
||||
) {
|
||||
if (!isset($customers[$change['customer']['id']]['address'])) {
|
||||
$customers[$change['customer']['id']]['address'] = array();
|
||||
}
|
||||
|
||||
$customers[
|
||||
$change['customer']['id']
|
||||
][
|
||||
'address'
|
||||
][
|
||||
$fieldsAddress['customerAddress'][$change['field']]
|
||||
] = self::newValue($change['newValue']);
|
||||
}
|
||||
|
||||
if (strripos($change['field'], 'custom_') !== false) {
|
||||
$customers[$change['customer']['id']]['customFields'][str_replace( 'custom_', '', $change['field'])] = self::newValue($change['newValue']);
|
||||
}
|
||||
|
||||
// email_marketing_unsubscribed_at old value will be null and new value will be datetime in
|
||||
// `Y-m-d H:i:s` format if customer was marked as unsubscribed in retailCRM
|
||||
if (isset($change['customer']['id']) && $change['field'] == 'email_marketing_unsubscribed_at') {
|
||||
if ($change['oldValue'] == null && is_string(self::newValue($change['newValue']))) {
|
||||
$customers[$change['customer']['id']]['subscribed'] = false;
|
||||
} elseif (is_string($change['oldValue']) && self::newValue($change['newValue']) == null) {
|
||||
$customers[$change['customer']['id']]['subscribed'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $customers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles corporate customers list from changes
|
||||
*
|
||||
* @param array $customerHistory
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function assemblyCorporateCustomer($customerHistory)
|
||||
{
|
||||
$fields = self::getMappingValues(array('customerCorporate', 'customerAddress'));
|
||||
$customersCorporate = array();
|
||||
|
||||
foreach ($customerHistory as $change) {
|
||||
$change['customer'] = self::removeEmpty($change['customer']);
|
||||
|
||||
if (
|
||||
isset($change['deleted'])
|
||||
&& $change['deleted']
|
||||
&& isset($customersCorporate[$change['customer']['id']])
|
||||
) {
|
||||
$customersCorporate[$change['customer']['id']]['deleted'] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($customersCorporate[$change['customer']['id']])) {
|
||||
if (
|
||||
isset($customersCorporate[$change['customer']['id']]['deleted'])
|
||||
&& $customersCorporate[$change['customer']['id']]['deleted']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$customersCorporate[$change['customer']['id']] = array_merge(
|
||||
$customersCorporate[$change['customer']['id']],
|
||||
$change['customer']
|
||||
);
|
||||
} else {
|
||||
$customersCorporate[$change['customer']['id']] = $change['customer'];
|
||||
}
|
||||
|
||||
if (
|
||||
isset($fields['customerCorporate'][$change['field']])
|
||||
&& $fields['customerCorporate'][$change['field']]
|
||||
) {
|
||||
$customersCorporate[
|
||||
$change['customer']['id']
|
||||
][
|
||||
$fields['customerCorporate'][$change['field']]
|
||||
] = self::newValue($change['newValue']);
|
||||
}
|
||||
|
||||
if (isset($fields['customerAddress'][$change['field']]) && $fields['customerAddress'][$change['field']]) {
|
||||
if (empty($customersCorporate[$change['customer']['id']]['address'])) {
|
||||
$customersCorporate[$change['customer']['id']]['address'] = array();
|
||||
}
|
||||
|
||||
$customersCorporate[
|
||||
$change['customer']['id']
|
||||
][
|
||||
'address'
|
||||
][
|
||||
$fields['customerAddress'][$change['field']]
|
||||
] = self::newValue($change['newValue']);
|
||||
}
|
||||
|
||||
if ($change['field'] == 'address') {
|
||||
$customersCorporate[
|
||||
$change['customer']['id']
|
||||
]['address'] = array_merge($change['address'], self::newValue($change['newValue']));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($customersCorporate as $id => &$customer) {
|
||||
if (empty($customer['id']) && !empty($id)) {
|
||||
$customer['id'] = $id;
|
||||
$customer['deleted'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $customersCorporate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns mapping data for retailCRM entities. Used to assembly entities from history.
|
||||
*
|
||||
* @param array $groupFilter
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function getMappingValues($groupFilter = array())
|
||||
{
|
||||
$fields = array();
|
||||
$mappingFile = realpath(WC_Integration_Retailcrm::checkCustomFile('config/objects.xml'));
|
||||
|
||||
if (file_exists($mappingFile)) {
|
||||
$objects = simplexml_load_file($mappingFile);
|
||||
|
||||
foreach ($objects->fields->field as $object) {
|
||||
if (empty($groupFilter) || in_array($object["group"], $groupFilter)) {
|
||||
$fields[(string)$object["group"]][(string)$object["id"]] = (string)$object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value accessor
|
||||
*
|
||||
* @param array $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function newValue($value)
|
||||
{
|
||||
if (isset($value['code'])) {
|
||||
return $value['code'];
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array without values which are considered empty
|
||||
*
|
||||
* @param array|\ArrayAccess $inputArray
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function removeEmpty($inputArray)
|
||||
{
|
||||
$outputArray = array();
|
||||
|
||||
if (!empty($inputArray)) {
|
||||
foreach ($inputArray as $key => $element) {
|
||||
if (!empty($element) || $element === 0 || $element === '0') {
|
||||
if (is_array($element)) {
|
||||
$element = self::removeEmpty($element);
|
||||
}
|
||||
|
||||
$outputArray[$key] = $element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $outputArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out history by these terms:
|
||||
* - Changes from current API key will be added only if CMS changes are more actual than history.
|
||||
* - All other changes will be merged as usual.
|
||||
* It fixes these problems:
|
||||
* - Changes from current API key are merged when it's not needed.
|
||||
* - Changes from CRM can overwrite more actual changes from CMS due to ignoring current API key changes.
|
||||
*
|
||||
* @param array $historyEntries Raw history from CRM
|
||||
* @param string $recordType Entity field name, e.g. `customer` or `order`.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function filterHistory($historyEntries, $recordType)
|
||||
{
|
||||
$history = array();
|
||||
$organizedHistory = array();
|
||||
$notOurChanges = array();
|
||||
|
||||
foreach ($historyEntries as $entry) {
|
||||
if (!isset($entry[$recordType]['externalId'])) {
|
||||
if (
|
||||
$entry['source'] == 'api'
|
||||
&& isset($change['apiKey']['current'])
|
||||
&& $entry['apiKey']['current'] == true
|
||||
&& $entry['field'] != 'externalId'
|
||||
) {
|
||||
continue;
|
||||
} else {
|
||||
$history[] = $entry;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$externalId = $entry[$recordType]['externalId'];
|
||||
$field = $entry['field'];
|
||||
|
||||
if (!isset($organizedHistory[$externalId])) {
|
||||
$organizedHistory[$externalId] = array();
|
||||
}
|
||||
|
||||
if (!isset($notOurChanges[$externalId])) {
|
||||
$notOurChanges[$externalId] = array();
|
||||
}
|
||||
|
||||
if (
|
||||
$entry['source'] == 'api'
|
||||
&& isset($entry['apiKey']['current'])
|
||||
&& $entry['apiKey']['current'] == true
|
||||
) {
|
||||
if (isset($notOurChanges[$externalId][$field]) || $entry['field'] == 'externalId') {
|
||||
$organizedHistory[$externalId][] = $entry;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$organizedHistory[$externalId][] = $entry;
|
||||
$notOurChanges[$externalId][$field] = true;
|
||||
}
|
||||
}
|
||||
|
||||
unset($notOurChanges);
|
||||
|
||||
foreach ($organizedHistory as $historyChunk) {
|
||||
$history = array_merge($history, $historyChunk);
|
||||
}
|
||||
|
||||
return $history;
|
||||
}
|
||||
}
|
212
src/include/components/class-wc-retailcrm-logger.php
Normal file
212
src/include/components/class-wc-retailcrm-logger.php
Normal file
|
@ -0,0 +1,212 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Logger') && class_exists('WC_Log_Levels')) :
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Logger - Allows display important debug information.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WC_Retailcrm_Logger
|
||||
{
|
||||
/** @var string */
|
||||
private const HANDLE = 'retailcrm';
|
||||
public const REQUEST = 'REQUEST';
|
||||
public const RESPONSE = 'RESPONSE';
|
||||
public CONST EXCEPTION = 'EXCEPTION';
|
||||
|
||||
/**
|
||||
* @var \WC_Logger_Interface $instance
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var array $additionalHandlers
|
||||
*/
|
||||
private static $additionalHandlers;
|
||||
|
||||
/**
|
||||
* @var string $logIdentifier
|
||||
*/
|
||||
private static $logIdentifier;
|
||||
|
||||
/**
|
||||
* @var string $currentHook
|
||||
*/
|
||||
private static $currentHook;
|
||||
|
||||
/**
|
||||
* @var float $startTime
|
||||
*/
|
||||
private static $startTime;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
private static function getInstance(): WC_Logger_Interface
|
||||
{
|
||||
if (!static::$instance instanceof WC_Logger) {
|
||||
static::$instance = new WC_Logger(self::$additionalHandlers);
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
public static function setAdditionalHandlers(array $additionalHandlers): void
|
||||
{
|
||||
self::$additionalHandlers = $additionalHandlers;
|
||||
}
|
||||
|
||||
public static function setHook(string $action, $id = null): void
|
||||
{
|
||||
static::$currentHook = $id === null ? $action : sprintf('%s-%d', $action, (int) $id);
|
||||
}
|
||||
|
||||
private static function getIdentifier(): string
|
||||
{
|
||||
if (!is_string(static::$logIdentifier)) {
|
||||
static::$logIdentifier = substr(wp_generate_uuid4(), 0, 8);
|
||||
}
|
||||
|
||||
return static::$logIdentifier;
|
||||
}
|
||||
|
||||
private static function getStartTime(): float
|
||||
{
|
||||
if (!is_float(static::$startTime)) {
|
||||
static::$startTime = microtime(true);
|
||||
}
|
||||
|
||||
return static::$startTime;
|
||||
}
|
||||
|
||||
public static function exception(string $method, Throwable $exception, string $additionalMessage = ''): void
|
||||
{
|
||||
self::error(
|
||||
$method,
|
||||
sprintf(
|
||||
'%s%s - Exception in file %s on line %s',
|
||||
$additionalMessage,
|
||||
$exception->getMessage(),
|
||||
$exception->getFile(),
|
||||
$exception->getLine()
|
||||
),
|
||||
['trace' => $exception->getTraceAsString()],
|
||||
self::EXCEPTION
|
||||
);
|
||||
}
|
||||
|
||||
public static function error(string $method, string $message, array $context = [], $type = null): void
|
||||
{
|
||||
self::log($method, $message, $context, $type, WC_Log_Levels::ERROR);
|
||||
}
|
||||
|
||||
public static function info(string $method, string $message, array $context = [], $type = null): void
|
||||
{
|
||||
self::log($method, $message, $context, $type, WC_Log_Levels::INFO);
|
||||
}
|
||||
|
||||
private static function log(string $method, string $message, array $context = [], $type = null, $level = 'info'): void
|
||||
{
|
||||
$time = self::getStartTime();
|
||||
$context['time'] = round((microtime(true) - $time), 3);
|
||||
$context['source'] = self::HANDLE;
|
||||
|
||||
$message = sprintf(
|
||||
'%s [%s] <%s> %s=> %s',
|
||||
self::getIdentifier(),
|
||||
self::$currentHook,
|
||||
$method,
|
||||
$type ? $type . ' ' : '',
|
||||
$message
|
||||
);
|
||||
|
||||
self::getInstance()->log($level, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts information useful for logs from an object
|
||||
*
|
||||
* @param $object
|
||||
* @return array
|
||||
*/
|
||||
public static function formatWcObject($object): array
|
||||
{
|
||||
if ($object instanceof WC_Order) {
|
||||
return self::formatWcOrder($object);
|
||||
}
|
||||
|
||||
if ($object instanceof WC_Customer) {
|
||||
return self::formatWcCustomer($object);
|
||||
}
|
||||
|
||||
if (is_object($object)) {
|
||||
return method_exists($object, 'get_data') ? (array_filter($object->get_data())) : [$object];
|
||||
}
|
||||
|
||||
return [$object];
|
||||
}
|
||||
|
||||
public static function formatWcOrder(WC_Order $order) {
|
||||
return [
|
||||
'id' => $order->get_id(),
|
||||
'status' => $order->get_status(),
|
||||
'date_modified' => $order->get_date_modified(),
|
||||
'total' => $order->get_total(),
|
||||
'shipping' => [
|
||||
'first_name' => $order->get_shipping_first_name(),
|
||||
'last_name' => $order->get_shipping_last_name(),
|
||||
'company' => $order->get_shipping_company(),
|
||||
'address_1' => $order->get_shipping_address_1(),
|
||||
'address_2' => $order->get_shipping_address_2(),
|
||||
'city' => $order->get_shipping_city(),
|
||||
'state' => $order->get_shipping_state(),
|
||||
'postcode' => $order->get_shipping_postcode(),
|
||||
'country' => $order->get_shipping_country(),
|
||||
'phone' => method_exists($order, 'get_shipping_phone') ? $order->get_shipping_phone() : '',
|
||||
],
|
||||
'billing' => [
|
||||
'phone' => $order->get_billing_phone()
|
||||
],
|
||||
'email' => $order->get_billing_email(),
|
||||
'payment_method_title' => $order->get_payment_method_title(),
|
||||
'date_paid' => $order->get_date_paid(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function formatWcCustomer(WC_Customer $customer)
|
||||
{
|
||||
return [
|
||||
'id' => $customer->get_id(),
|
||||
'date_modified' => $customer->get_date_modified(),
|
||||
'firstName' => $customer->get_first_name(),
|
||||
'lastName' => $customer->get_last_name(),
|
||||
'email' => $customer->get_email(),
|
||||
'display_name' => $customer->get_display_name(),
|
||||
'role' => $customer->get_role(),
|
||||
'username' => $customer->get_username(),
|
||||
'shipping' => [
|
||||
'first_name' => $customer->get_shipping_first_name(),
|
||||
'last_name' => $customer->get_shipping_last_name(),
|
||||
'company' => $customer->get_shipping_company(),
|
||||
'address_1' => $customer->get_shipping_address_1(),
|
||||
'address_2' => $customer->get_shipping_address_2(),
|
||||
'city' => $customer->get_shipping_city(),
|
||||
'state' => $customer->get_shipping_state(),
|
||||
'postcode' => $customer->get_shipping_postcode(),
|
||||
'country' => $customer->get_shipping_country(),
|
||||
'phone' => method_exists($customer, 'get_shipping_phone') ? $customer->get_shipping_phone() : '',
|
||||
],
|
||||
'billing' => [
|
||||
'phone' => $customer->get_billing_phone()
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
endif;
|
108
src/include/components/class-wc-retailcrm-loyalty-form.php
Normal file
108
src/include/components/class-wc-retailcrm-loyalty-form.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Loyalty')) :
|
||||
|
||||
|
||||
class WC_Retailcrm_Loyalty_Form
|
||||
{
|
||||
public function getRegistrationForm($phone = '', $loyaltyTerms = '', $loyaltyPersonal = '')
|
||||
{
|
||||
$htmlLoyaltyTerms = $loyaltyTerms !== ''
|
||||
? sprintf(
|
||||
'<p><input type="checkbox" name="terms" id="termsLoyalty" required>%s<a id="terms-popup" class="popup-open-loyalty" href="#">%s</a>.</p>',
|
||||
__(' I agree with ', 'retailcrm'),
|
||||
__('loyalty program terms', 'retailcrm')
|
||||
)
|
||||
: ''
|
||||
;
|
||||
|
||||
$htmlLoyaltyPersonal = $loyaltyPersonal !== ''
|
||||
? sprintf(
|
||||
'<p><input type="checkbox" name="privacy" id="privacyLoyalty" required>%s<a id="privacy-popup" class="popup-open-loyalty" href="#">%s</a>.</p>',
|
||||
__(' I agree with ', 'retailcrm'),
|
||||
__('terms of personal data processing', 'retailcrm')
|
||||
)
|
||||
: ''
|
||||
;
|
||||
|
||||
|
||||
return sprintf(
|
||||
'
|
||||
<form id="loyaltyRegisterForm" method="post">
|
||||
<p>%s</p>
|
||||
%s
|
||||
%s
|
||||
<p><input type="text" name="phone" id="phoneLoyalty" placeholder="%s" value="%s" required></p>
|
||||
<p><input type="submit" value="%s"></p>
|
||||
</form>
|
||||
<div class="popup-fade-loyalty">
|
||||
<div class="popup-loyalty">
|
||||
<a class="popup-close-loyalty" href="#">%s</a>
|
||||
<br>
|
||||
<div id="popup-loyalty-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
',
|
||||
__('To register in the loyalty program, fill in the form:', 'retailcrm'),
|
||||
$htmlLoyaltyTerms,
|
||||
$htmlLoyaltyPersonal,
|
||||
__('Phone', 'retailcrm'),
|
||||
$phone,
|
||||
__('Send', 'retailcrm'),
|
||||
__('Close', 'retailcrm')
|
||||
);
|
||||
}
|
||||
|
||||
public function getActivationForm()
|
||||
{
|
||||
return sprintf('
|
||||
<form id="loyaltyActivateForm" method="post">
|
||||
<p><input type="checkbox" id="loyaltyActiveCheckbox" name="loyaltyCheckbox" required> %s</p>
|
||||
<input type="submit" value="%s">
|
||||
</form>',
|
||||
__('Activate participation in the loyalty program', 'retailcrm'),
|
||||
__('Send', 'retailcrm')
|
||||
);
|
||||
}
|
||||
|
||||
public function getInfoLoyalty(array $loyaltyAccount)
|
||||
{
|
||||
$data = [
|
||||
'<b>' . __('Bonus account', 'retailcrm') . '</b>',
|
||||
__('Participation ID: ', 'retailcrm') . $loyaltyAccount['id'],
|
||||
__('Current level: ', 'retailcrm') . $loyaltyAccount['level']['name'],
|
||||
__('Bonuses on the account: ', 'retailcrm') . $loyaltyAccount['amount'],
|
||||
__('Bonus card number: ' , 'retailcrm') . ($loyaltyAccount['cardNumber'] ?? __('The card is not linked', 'retailcrm')),
|
||||
__('Date of registration: ', 'retailcrm') . $loyaltyAccount['activatedAt'],
|
||||
'<br>',
|
||||
'<b>' . __('Current level rules', 'retailcrm') . '</b>',
|
||||
__('Required amount of purchases to move to the next level: ', 'retailcrm') . $loyaltyAccount['nextLevelSum'] . ' ' . $loyaltyAccount['loyalty']['currency']
|
||||
];
|
||||
|
||||
switch ($loyaltyAccount['level']['type']) {
|
||||
case 'bonus_converting':
|
||||
$data[] = sprintf(__('Ordinary products: accrual of 1 bonus for each %s %s', 'retailcrm'), $loyaltyAccount['level']['privilegeSize'], $loyaltyAccount['loyalty']['currency']);
|
||||
$data[] = sprintf(__('Promotional products: accrual of 1 bonus for each %s %s', 'retailcrm'), $loyaltyAccount['level']['privilegeSizePromo'], $loyaltyAccount['loyalty']['currency']);
|
||||
break;
|
||||
case 'bonus_percent':
|
||||
$data[] = sprintf(__('Ordinary products: bonus accrual in the amount of %s%% of the purchase amount', 'retailcrm'), $loyaltyAccount['level']['privilegeSize']);
|
||||
$data[] = sprintf(__('Promotional products: bonus accrual in the amount of %s%% of the purchase amount', 'retailcrm'), $loyaltyAccount['level']['privilegeSizePromo']);
|
||||
break;
|
||||
case 'discount':
|
||||
$data[] = sprintf(__('Ordinary products: %s%% discount', 'retailcrm'), $loyaltyAccount['level']['privilegeSize']);
|
||||
$data[] = sprintf(__('Promotional products: %s%% discount', 'retailcrm'), $loyaltyAccount['level']['privilegeSizePromo']);
|
||||
break;
|
||||
}
|
||||
|
||||
$result = '';
|
||||
|
||||
foreach ($data as $line) {
|
||||
$result .= "<p style='line-height: 1'>$line</p>";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
endif;
|
45
src/include/customer/class-wc-retailcrm-customer-address.php
Normal file
45
src/include/customer/class-wc-retailcrm-customer-address.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Customer_Address - Builds a billing address for a customer.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Customer_Address extends WC_Retailcrm_Abstracts_Address
|
||||
{
|
||||
/**
|
||||
* @param WC_Customer $customer
|
||||
* @param \WC_Order|null $order
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function build($customer, $order = null)
|
||||
{
|
||||
$address = $this->getCustomerAddress($customer, $order);
|
||||
|
||||
if (!empty($address)) {
|
||||
$customerAddress = apply_filters(
|
||||
'retailcrm_process_customer_address',
|
||||
WC_Retailcrm_Plugin::clearArray($address),
|
||||
$customer,
|
||||
$order
|
||||
);
|
||||
|
||||
$this->setDataFields($customerAddress);
|
||||
} else {
|
||||
WC_Retailcrm_Logger::error(
|
||||
__METHOD__,
|
||||
'Error: Customer address is empty',
|
||||
['wc_customer' => WC_Retailcrm_Logger::formatWcObject($customer)]
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Customer_Corporate_Address - Builds a billing address for a corporate customer.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Customer_Corporate_Address extends WC_Retailcrm_Abstracts_Address
|
||||
{
|
||||
/** @var bool $isMain */
|
||||
protected $isMain = true;
|
||||
|
||||
/**
|
||||
* @param bool $isMain
|
||||
*
|
||||
* @return WC_Retailcrm_Customer_Corporate_Address
|
||||
*/
|
||||
public function setIsMain($isMain)
|
||||
{
|
||||
$this->isMain = $isMain;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Customer $customer
|
||||
* @param \WC_Order|null $order
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function build($customer, $order = null)
|
||||
{
|
||||
$address = $this->getCustomerAddress($customer, $order);
|
||||
|
||||
if (!empty($address)) {
|
||||
$address['isMain'] = $this->isMain;
|
||||
|
||||
$corporateCustomerAddress = apply_filters(
|
||||
'retailcrm_process_customer_corporate_address',
|
||||
WC_Retailcrm_Plugin::clearArray(array_merge(
|
||||
$address,
|
||||
['isMain' => $this->isMain]
|
||||
)),
|
||||
$customer
|
||||
);
|
||||
|
||||
$this->setDataFields($corporateCustomerAddress);
|
||||
} else {
|
||||
WC_Retailcrm_Logger::error(
|
||||
__METHOD__,
|
||||
'Error: Corporate Customer address is empty.',
|
||||
['wc_customer' => WC_Retailcrm_Logger::formatWcObject($customer)]
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_WC_Customer_Builder - It converts retailCRM customer data (array) into WC_Customer.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_WC_Customer_Builder extends WC_Retailcrm_Abstract_Builder
|
||||
{
|
||||
/**
|
||||
* @var \WC_Customer $customer
|
||||
*/
|
||||
private $customer;
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_WC_Customer_Builder constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $firstName
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFirstName($firstName)
|
||||
{
|
||||
$this->data['firstName'] = $firstName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $lastName
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLastName($lastName)
|
||||
{
|
||||
$this->data['lastName'] = $lastName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmail($email)
|
||||
{
|
||||
$this->data['email'] = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $externalId
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setExternalId($externalId)
|
||||
{
|
||||
$this->data['externalId'] = $externalId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $phones
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPhones($phones)
|
||||
{
|
||||
if (self::isPhonesArrayValid($phones)) {
|
||||
$this->data['phones'] = $phones;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $address
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAddress($address)
|
||||
{
|
||||
if (is_array($address)) {
|
||||
$this->data['address'] = $address;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WC_Customer $customer
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWcCustomer($customer)
|
||||
{
|
||||
if ($customer instanceof WC_Customer) {
|
||||
$this->customer = $customer;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets provided externalId and loads associated customer from DB (it it exists there).
|
||||
* Returns true if everything went find; returns false if customer wasn't found.
|
||||
*
|
||||
* @param string $externalId
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function loadExternalId($externalId)
|
||||
{
|
||||
try {
|
||||
$wcCustomer = new WC_Customer($externalId);
|
||||
} catch (\Exception $exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->setExternalId($externalId);
|
||||
$this->setWcCustomer($wcCustomer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
$this->customer = new WC_Customer();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill WC_Customer fields with customer data from RetailCRM.
|
||||
* If field is not present in retailCRM customer - it will remain unchanged.
|
||||
*
|
||||
* @return $this|\WC_Retailcrm_Builder_Interface
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$this->checkBuilderValidity();
|
||||
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Building WC_Customer from data',
|
||||
['customer_data' => $this->data]
|
||||
);
|
||||
|
||||
$this->customer->set_first_name($this->dataValue('firstName', $this->customer->get_first_name()));
|
||||
$this->customer->set_last_name($this->dataValue('lastName', $this->customer->get_last_name()));
|
||||
$this->customer->set_billing_email($this->dataValue('email', $this->customer->get_billing_email()));
|
||||
$phones = $this->dataValue('phones', []);
|
||||
|
||||
if ((is_array($phones) || $phones instanceof Countable) && count($phones) > 0) {
|
||||
$phoneData = reset($phones);
|
||||
|
||||
if (is_array($phoneData) && isset($phoneData['number'])) {
|
||||
$this->customer->set_billing_phone($phoneData['number']);
|
||||
}
|
||||
} elseif (is_string($phones) || is_numeric($phones)) {
|
||||
$this->customer->set_billing_phone($phones);
|
||||
}
|
||||
|
||||
$address = $this->dataValue('address');
|
||||
|
||||
if (!empty($address)) {
|
||||
$this->customer->set_billing_state(self::arrayValue(
|
||||
$address,
|
||||
'region',
|
||||
$this->customer->get_billing_state()
|
||||
));
|
||||
$this->customer->set_billing_postcode(self::arrayValue(
|
||||
$address,
|
||||
'index',
|
||||
$this->customer->get_billing_postcode()
|
||||
));
|
||||
$this->customer->set_billing_country(self::arrayValue(
|
||||
$address,
|
||||
'country',
|
||||
$this->customer->get_billing_country()
|
||||
));
|
||||
$this->customer->set_billing_city(self::arrayValue(
|
||||
$address,
|
||||
'city',
|
||||
$this->customer->get_billing_city()
|
||||
));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|\WC_Customer|null
|
||||
*/
|
||||
public function getResult()
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if internal state is not ready for data building.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function checkBuilderValidity()
|
||||
{
|
||||
if (empty($this->data)) {
|
||||
throw new \RuntimeException('Empty data');
|
||||
}
|
||||
|
||||
if (!is_array($this->data)) {
|
||||
throw new \RuntimeException('Data must be an array');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if provided variable contains array with customer phones.
|
||||
*
|
||||
* @param mixed $phones
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isPhonesArrayValid($phones)
|
||||
{
|
||||
if (!is_array($phones)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($phones as $phone) {
|
||||
if (!is_array($phone) || count($phone) != 1 || !array_key_exists('number', $phone)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
214
src/include/functions.php
Normal file
214
src/include/functions.php
Normal file
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
|
||||
if (! defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
function get_wc_shipping_methods_by_zones($enhanced = false)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$shippingZones = WC_Shipping_Zones::get_zones();
|
||||
$defaultZone = WC_Shipping_Zones::get_zone_by();
|
||||
|
||||
$shippingZones[$defaultZone->get_id()] = [
|
||||
$defaultZone->get_data(),
|
||||
'zone_id' => $defaultZone->get_id(),
|
||||
'formatted_zone_location' => $defaultZone->get_formatted_location(),
|
||||
'shipping_methods' => $defaultZone->get_shipping_methods(false)
|
||||
];
|
||||
|
||||
if ($shippingZones) {
|
||||
foreach ($shippingZones as $code => $shippingZone) {
|
||||
foreach ($shippingZone['shipping_methods'] as $key => $shipping_method) {
|
||||
$shipping_methods = [
|
||||
'id' => $shipping_method->id,
|
||||
'instance_id' => $shipping_method->instance_id,
|
||||
'title' => $shipping_method->title
|
||||
];
|
||||
|
||||
if ($enhanced) {
|
||||
$shipping_code = $shipping_method->id;
|
||||
} else {
|
||||
$shipping_code = $shipping_method->id . ':' . $shipping_method->instance_id;
|
||||
}
|
||||
|
||||
if (!isset($result[$shipping_code])) {
|
||||
$result[$shipping_code] = [
|
||||
'name' => $shipping_method->method_title,
|
||||
'enabled' => $shipping_method->enabled,
|
||||
'description' => $shipping_method->method_description,
|
||||
'title' => $shipping_method->title
|
||||
];
|
||||
}
|
||||
|
||||
if ($enhanced) {
|
||||
$result[$shipping_method->id]['shipping_methods'][$shipping_method->id . ':' . $shipping_method->instance_id] = $shipping_methods;
|
||||
unset($shipping_methods);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
function get_wc_shipping_methods()
|
||||
{
|
||||
$wc_shipping = WC_Shipping::instance();
|
||||
$shipping_methods = $wc_shipping->get_shipping_methods();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($shipping_methods as $code => $shipping) {
|
||||
$result[$code] = [
|
||||
'name' => $shipping->method_title,
|
||||
'enabled' => $shipping->enabled,
|
||||
'description' => $shipping->method_description,
|
||||
'title' => $shipping->title ? $shipping->title : $shipping->method_title
|
||||
];
|
||||
}
|
||||
|
||||
return apply_filters('retailcrm_shipping_list', WC_Retailcrm_Plugin::clearArray($result));
|
||||
}
|
||||
|
||||
function retailcrm_get_delivery_service($method_id, $instance_id)
|
||||
{
|
||||
$shippings_by_zone = get_wc_shipping_methods_by_zones(true);
|
||||
$method = explode(':', $method_id);
|
||||
$method_id = $method[0];
|
||||
$shipping = $shippings_by_zone[$method_id] ?? [];
|
||||
|
||||
if ($shipping && isset($shipping['shipping_methods'][$method_id . ':' . $instance_id])) {
|
||||
return $shipping['shipping_methods'][$method_id . ':' . $instance_id];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @param $settings
|
||||
*
|
||||
* @return false|WC_Product|null
|
||||
*/
|
||||
function retailcrm_get_wc_product($id, $settings)
|
||||
{
|
||||
if (
|
||||
isset($settings['bind_by_sku'])
|
||||
&& $settings['bind_by_sku'] == WC_Retailcrm_Base::YES
|
||||
) {
|
||||
$id = wc_get_product_id_by_sku($id);
|
||||
}
|
||||
|
||||
return wc_get_product($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if either wordpress debug mode or module debugging is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function retailcrm_is_debug()
|
||||
{
|
||||
$options = get_option(WC_Retailcrm_Base::$option_key);
|
||||
|
||||
if (isset($options['debug_mode']) === true && $options['debug_mode'] === WC_Retailcrm_Base::YES) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if current page equals wp-login
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_wplogin()
|
||||
{
|
||||
$ABSPATH_MY = str_replace(['\\','/'], DIRECTORY_SEPARATOR, ABSPATH);
|
||||
|
||||
return (
|
||||
(in_array($ABSPATH_MY . 'wp-login.php', get_included_files())
|
||||
|| in_array($ABSPATH_MY . 'wp-register.php', get_included_files()))
|
||||
|| (isset($_GLOBALS['pagenow']) && $GLOBALS['pagenow'] === 'wp-login.php')
|
||||
|| $_SERVER['PHP_SELF'] == '/wp-login.php'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a tax class with a standart rate is selected, woocommerce_shipping_tax_class = ''
|
||||
* If a tax class with a zero rate is selected, woocommerce_shipping_tax_class = zero-rate
|
||||
* If a tax class with a reduced rate is selected, woocommerce_shipping_tax_class = reduced-rate
|
||||
* If the tax is calculated based on the items in the cart, woocommerce_shipping_tax_class = inherit
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function getShippingRate()
|
||||
{
|
||||
if (!isset(WC()->cart)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$shippingRates = WC_Tax::get_shipping_tax_rates();
|
||||
|
||||
// Only one tax can be selected for shipping
|
||||
if (is_array($shippingRates)) {
|
||||
$shippingRates = array_shift($shippingRates);
|
||||
}
|
||||
|
||||
return $shippingRates['rate'] ?? $shippingRates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order item rate.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function getOrderItemRate($wcOrder)
|
||||
{
|
||||
$orderItemTax = $wcOrder->get_taxes();
|
||||
|
||||
if (is_array($orderItemTax)) {
|
||||
$orderItemTax = array_shift($orderItemTax);
|
||||
}
|
||||
|
||||
return $orderItemTax instanceof WC_Order_Item_Tax ? $orderItemTax->get_rate_percent() : null;
|
||||
}
|
||||
|
||||
function calculatePriceExcludingTax($priceIncludingTax, $rate)
|
||||
{
|
||||
return round($priceIncludingTax / (1 + $rate / 100), wc_get_price_decimals());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking the use of HPOS.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
function useHpos()
|
||||
{
|
||||
return class_exists(Automattic\WooCommerce\Utilities\OrderUtil::class)
|
||||
&& Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
|
||||
}
|
||||
|
||||
function isLoyaltyActivate($settings)
|
||||
{
|
||||
return isset($settings['loyalty']) && $settings['loyalty'] === WC_Retailcrm_Base::YES;
|
||||
}
|
||||
|
||||
function isCorporateUserActivate($settings)
|
||||
{
|
||||
return isset($settings['corporate_enabled']) && $settings['corporate_enabled'] === WC_Retailcrm_Base::YES;
|
||||
}
|
||||
|
||||
function isCorporateOrder($wcCustomer, $wcOrder)
|
||||
{
|
||||
return !empty($wcCustomer->get_billing_company()) || !empty($wcOrder->get_billing_company());
|
||||
}
|
||||
|
||||
function getOptionByCode($optionName)
|
||||
{
|
||||
return get_option(WC_Retailcrm_Base::$option_key)[$optionName] ?? null;
|
||||
}
|
254
src/include/icml/class-wc-retailcrm-icml-writer.php
Normal file
254
src/include/icml/class-wc-retailcrm-icml-writer.php
Normal file
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
|
||||
if (!class_exists('WC_Retailcrm_Icml_Writer')) :
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Icml - Generate ICML file (catalog).
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Icml_Writer
|
||||
{
|
||||
private $writer;
|
||||
|
||||
public function __construct($tmpFile)
|
||||
{
|
||||
$this->writer = new \XMLWriter();
|
||||
$this->writer->openUri($tmpFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write HEAD in ICML catalog.
|
||||
*
|
||||
* @param string $shop
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeHead(string $shop)
|
||||
{
|
||||
$this->writer->startDocument('1.0', 'UTF-8');
|
||||
$this->writer->startElement('yml_catalog'); // start <yml_catalog>
|
||||
$this->writer->writeAttribute('date', date('Y-m-d H:i:s'));
|
||||
$this->writer->startElement('shop'); // start <shop>
|
||||
$this->writer->WriteElement('name', $shop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write categories in ICML catalog.
|
||||
*
|
||||
* @param array $categories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeCategories(array $categories)
|
||||
{
|
||||
$this->writer->startElement('categories'); // start <categories>
|
||||
|
||||
$this->addCategories($categories);
|
||||
|
||||
$this->writer->endElement(); // end </categories>
|
||||
}
|
||||
|
||||
/**
|
||||
* Add category in ICML catalog.
|
||||
*
|
||||
* @param array $categories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addCategories(array $categories)
|
||||
{
|
||||
foreach ($categories as $category) {
|
||||
if (!array_key_exists('name', $category) || !array_key_exists('id', $category)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->writer->startElement('category'); // start <category>
|
||||
|
||||
$this->writer->writeAttribute('id', $category['id']);
|
||||
|
||||
if (array_key_exists('parentId', $category) && 0 < $category['parentId']) {
|
||||
$this->writer->writeAttribute('parentId', $category['parentId']);
|
||||
}
|
||||
|
||||
$this->writer->writeElement('name', $category['name']);
|
||||
|
||||
if (array_key_exists('picture', $category) && $category['picture']) {
|
||||
$this->writer->writeElement('picture', $category['picture']);
|
||||
}
|
||||
|
||||
$this->writer->endElement(); // end </category>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write offers in ICML catalog.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeOffers($offers)
|
||||
{
|
||||
$this->writer->startElement('offers'); // start <offers>
|
||||
|
||||
$this->addOffers($offers);
|
||||
|
||||
$this->writer->endElement(); // end </offers>
|
||||
}
|
||||
|
||||
/**
|
||||
* Add offer in ICML catalog.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addOffers($offers)
|
||||
{
|
||||
foreach ($offers as $offer) {
|
||||
if (!array_key_exists('id', $offer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->writer->startElement('offer'); // start <offer>
|
||||
|
||||
if (!array_key_exists('productId', $offer) || empty($offer['productId'])) {
|
||||
$offer['productId'] = $offer['id'];
|
||||
}
|
||||
|
||||
$this->writer->writeAttribute('id', $offer['id']);
|
||||
$this->writer->writeAttribute('productId', $offer['productId']);
|
||||
$this->writer->writeAttribute('quantity', (int) $offer['quantity'] ?? 0);
|
||||
$this->writer->writeAttribute('type', $offer['type']);
|
||||
|
||||
if (isset($offer['categoryId'])) {
|
||||
if (is_array($offer['categoryId'])) {
|
||||
foreach ($offer['categoryId'] as $categoryId) {
|
||||
$this->writer->writeElement('categoryId', $categoryId);
|
||||
}
|
||||
} else {
|
||||
$this->writer->writeElement('categoryId', $offer['$categoryId']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($offer['picture'])) {
|
||||
foreach ($offer['picture'] as $urlImage) {
|
||||
$this->writer->writeElement('picture', $urlImage);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($offer['name'])) {
|
||||
$offer['name'] = __('Untitled', 'retailcrm');
|
||||
}
|
||||
|
||||
if (empty($offer['productName'])) {
|
||||
$offer['productName'] = $offer['name'];
|
||||
}
|
||||
|
||||
unset($offer['id'], $offer['productId'], $offer['categoryId'], $offer['quantity'], $offer['picture']);
|
||||
|
||||
$this->writeOffersProperties($offer);
|
||||
|
||||
if (!empty($offer['params'])) {
|
||||
$this->writeOffersParams($offer['params']);
|
||||
}
|
||||
|
||||
if (!empty($offer['dimensions'])) {
|
||||
$this->writer->writeElement('dimensions', $offer['dimensions']);
|
||||
}
|
||||
|
||||
if (!empty($offer['weight'])) {
|
||||
$this->writer->writeElement('weight', $offer['weight']);
|
||||
}
|
||||
|
||||
if (!empty($offer['tax'])) {
|
||||
$this->writer->writeElement('vatRate', $offer['tax']);
|
||||
}
|
||||
|
||||
$this->writer->endElement(); // end </offer>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set offer properties.
|
||||
*
|
||||
* @param array $offerProperties
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function writeOffersProperties(array $offerProperties)
|
||||
{
|
||||
foreach ($offerProperties as $key => $value) {
|
||||
if (!in_array($key, WC_Retailcrm_Icml::OFFER_PROPERTIES)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $element) {
|
||||
$this->writer->writeElement($key, $element);
|
||||
}
|
||||
} else {
|
||||
$this->writer->writeElement($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set offer params.
|
||||
*
|
||||
* @param array $offerParams
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function writeOffersParams(array $offerParams)
|
||||
{
|
||||
foreach ($offerParams as $param) {
|
||||
if (
|
||||
empty($param['code'])
|
||||
|| empty($param['name'])
|
||||
|| empty($param['value'])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->writer->startElement('param'); // start <param>
|
||||
|
||||
$this->writer->writeAttribute('code', $param['code']);
|
||||
$this->writer->writeAttribute('name', $param['name']);
|
||||
$this->writer->text($param['value']);
|
||||
|
||||
$this->writer->endElement(); // end </param>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write end tags in ICML catalog.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeEnd()
|
||||
{
|
||||
$this->writer->endElement(); // end </yml_catalog>
|
||||
$this->writer->endElement(); // end </shop>
|
||||
$this->writer->endDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save ICML catalog.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function formatXml($tmpfile)
|
||||
{
|
||||
$dom = dom_import_simplexml(simplexml_load_file($tmpfile))->ownerDocument;
|
||||
$dom->formatOutput = true;
|
||||
$formatted = $dom->saveXML();
|
||||
|
||||
unset($dom, $this->writer);
|
||||
|
||||
file_put_contents($tmpfile, $formatted);
|
||||
}
|
||||
}
|
||||
endif;
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Interface WC_Retailcrm_Builder_Interface - Main interface for builders. All builders.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
interface WC_Retailcrm_Builder_Interface {
|
||||
/**
|
||||
* Sets data into builder
|
||||
*
|
||||
* @param array|mixed $data
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setData($data);
|
||||
|
||||
/**
|
||||
* Returns data present in the builder
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function getData();
|
||||
|
||||
/**
|
||||
* This method should build result with data present in the builder.
|
||||
* It should return builder instance in spite of actual building result.
|
||||
* Any exception can be thrown in case of error. It should be processed accordingly.
|
||||
*
|
||||
* @return self
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function build();
|
||||
|
||||
/**
|
||||
* This method should reset builder state.
|
||||
* In other words, after calling reset() builder inner state should become identical to newly created builder's state.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Returns builder result. Can be anything (depends on builder).
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getResult();
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Customer_Switcher_Result - Holds modified order and customer which was set in the order.
|
||||
* If customer is null, then only order props was updated. Previous customer (if it was registered)
|
||||
* will be detached from this order.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Customer_Switcher_Result
|
||||
{
|
||||
/** @var \WC_Customer|null */
|
||||
private $wcCustomer;
|
||||
|
||||
/** @var \WC_Order $wcOrder */
|
||||
private $wcOrder;
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Customer_Switcher_Result constructor.
|
||||
*
|
||||
* @param \WC_Customer|null $wcCustomer
|
||||
* @param \WC_Order $wcOrder
|
||||
*/
|
||||
public function __construct($wcCustomer, $wcOrder)
|
||||
{
|
||||
$this->wcCustomer = $wcCustomer;
|
||||
$this->wcOrder = $wcOrder;
|
||||
|
||||
if (
|
||||
(!is_null($this->wcCustomer) && !($this->wcCustomer instanceof WC_Customer))
|
||||
|| !($this->wcOrder instanceof WC_Order)
|
||||
) {
|
||||
throw new \InvalidArgumentException(sprintf('Incorrect data provided to %s', __CLASS__));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WC_Customer|null
|
||||
*/
|
||||
public function getWcCustomer()
|
||||
{
|
||||
return $this->wcCustomer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WC_Order
|
||||
*/
|
||||
public function getWcOrder()
|
||||
{
|
||||
return $this->wcOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save customer (if exists) and order.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'Saving WC_Customer and WC_Order',
|
||||
[
|
||||
'wc_customer' => WC_Retailcrm_Logger::formatWcObject($this->wcCustomer),
|
||||
'wc_order' => WC_Retailcrm_Logger::formatWcObject($this->wcOrder),
|
||||
]
|
||||
);
|
||||
|
||||
if (!empty($this->wcCustomer) && $this->wcCustomer->get_id()) {
|
||||
$this->wcCustomer->save();
|
||||
}
|
||||
|
||||
if (!empty($this->wcOrder) && $this->wcOrder->get_id()) {
|
||||
$this->wcOrder->save();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Customer_Switcher_State - Holds WC_Retailcrm_Customer_Switcher state.
|
||||
* It exists only because we need to comply with builder interface.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Customer_Switcher_State
|
||||
{
|
||||
/** @var \WC_Order $wcOrder */
|
||||
private $wcOrder;
|
||||
|
||||
/** @var array */
|
||||
private $newCustomer;
|
||||
|
||||
/** @var array */
|
||||
private $newContact;
|
||||
|
||||
/** @var string $newCompanyName */
|
||||
private $newCompanyName;
|
||||
|
||||
/** @var array $companyAddress */
|
||||
private $companyAddress;
|
||||
|
||||
/**
|
||||
* @return \WC_Order
|
||||
*/
|
||||
public function getWcOrder()
|
||||
{
|
||||
return $this->wcOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WC_Order $wcOrder
|
||||
*
|
||||
* @return WC_Retailcrm_Customer_Switcher_State
|
||||
*/
|
||||
public function setWcOrder($wcOrder)
|
||||
{
|
||||
$this->wcOrder = $wcOrder;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getNewCustomer()
|
||||
{
|
||||
return $this->newCustomer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $newCustomer
|
||||
*
|
||||
* @return WC_Retailcrm_Customer_Switcher_State
|
||||
*/
|
||||
public function setNewCustomer($newCustomer)
|
||||
{
|
||||
$this->newCustomer = $newCustomer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getNewContact()
|
||||
{
|
||||
return $this->newContact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $newContact
|
||||
*
|
||||
* @return WC_Retailcrm_Customer_Switcher_State
|
||||
*/
|
||||
public function setNewContact($newContact)
|
||||
{
|
||||
$this->newContact = $newContact;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNewCompanyName()
|
||||
{
|
||||
return $this->newCompanyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $newCompanyName
|
||||
*
|
||||
* @return WC_Retailcrm_Customer_Switcher_State
|
||||
*/
|
||||
public function setNewCompanyName($newCompanyName)
|
||||
{
|
||||
$this->newCompanyName = $newCompanyName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCompanyAddress()
|
||||
{
|
||||
return $this->companyAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $companyAddress
|
||||
*
|
||||
* @return WC_Retailcrm_Customer_Switcher_State
|
||||
*/
|
||||
public function setCompanyAddress($companyAddress)
|
||||
{
|
||||
$this->companyAddress = $companyAddress;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $newCompany
|
||||
*
|
||||
* @return WC_Retailcrm_Customer_Switcher_State
|
||||
*/
|
||||
public function setNewCompany($newCompany)
|
||||
{
|
||||
if (isset($newCompany['name'])) {
|
||||
$this->setNewCompanyName($newCompany['name']);
|
||||
}
|
||||
|
||||
if (isset($newCompany['address']) && !empty($newCompany['address'])) {
|
||||
$this->setCompanyAddress($newCompany['address']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if current state may be processable (e.g. when customer or related data was changed).
|
||||
* It doesn't guarantee state validity.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function feasible()
|
||||
{
|
||||
return !(empty($this->newCustomer) && empty($this->newContact) && empty($this->newCompanyName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if state is not valid
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @return void
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if (empty($this->wcOrder)) {
|
||||
throw new \InvalidArgumentException('Empty WC_Order.');
|
||||
}
|
||||
|
||||
if (empty($this->newCustomer) && empty($this->newContact) && empty($this->newCompanyName)) {
|
||||
throw new \InvalidArgumentException('New customer, new contact and new company is empty.');
|
||||
}
|
||||
|
||||
if (!empty($this->newCustomer) && !empty($this->newContact)) {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
'State data - customer and contact',
|
||||
[
|
||||
'customer' => $this->getNewCustomer(),
|
||||
'contact' => $this->getNewContact(),
|
||||
]
|
||||
);
|
||||
|
||||
throw new \InvalidArgumentException(
|
||||
'Too much data in state - cannot determine which customer should be used.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
39
src/include/order/class-wc-retailcrm-order-address.php
Normal file
39
src/include/order/class-wc-retailcrm-order-address.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Order_Address - Build address for CRM order.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Order_Address extends WC_Retailcrm_Abstracts_Address
|
||||
{
|
||||
/**
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function build($order)
|
||||
{
|
||||
$address = $this->getOrderAddress($order);
|
||||
|
||||
if (!empty($address)) {
|
||||
$orderAddress = apply_filters(
|
||||
'retailcrm_process_order_address',
|
||||
WC_Retailcrm_Plugin::clearArray($address),
|
||||
$order
|
||||
);
|
||||
|
||||
$this->setDataFields($orderAddress);
|
||||
} else {
|
||||
WC_Retailcrm_Logger::error(__METHOD__, 'Error: Order address is empty');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
238
src/include/order/class-wc-retailcrm-order-item.php
Normal file
238
src/include/order/class-wc-retailcrm-order-item.php
Normal file
|
@ -0,0 +1,238 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Order_Item - Build items for CRM order.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data
|
||||
{
|
||||
/**
|
||||
* @var array order item
|
||||
*/
|
||||
protected $data = [
|
||||
'offer' => [],
|
||||
'productName' => '',
|
||||
'initialPrice' => 0.00,
|
||||
'quantity' => 0.00
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = [];
|
||||
|
||||
/** @var bool */
|
||||
public $cancelLoyalty = false;
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Order_Item constructor.
|
||||
*
|
||||
* @param array $settings
|
||||
*/
|
||||
public function __construct($settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order_Item_Product $item
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function build($item, $crmItem = null)
|
||||
{
|
||||
$decimalPlaces = wc_get_price_decimals();
|
||||
|
||||
// Calculate price and discount
|
||||
$price = $this->calculatePrice($item, $decimalPlaces);
|
||||
$discountPrice = $this->calculateDiscount($item, $price, $decimalPlaces, $crmItem);
|
||||
|
||||
$data['productName'] = $item['name'];
|
||||
$data['initialPrice'] = $price;
|
||||
$data['quantity'] = (double)$item['qty'];
|
||||
|
||||
$itemId = ($item['variation_id'] > 0) ? $item['variation_id'] : $item['product_id'];
|
||||
$data['externalIds'] = [
|
||||
[
|
||||
'code' => 'woocomerce',
|
||||
'value' => $itemId . '_' . $item->get_id(),
|
||||
]
|
||||
];
|
||||
|
||||
$this->setDataFields($data);
|
||||
$this->setOffer($item);
|
||||
$this->setField('discountManualAmount', $discountPrice);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order_Item_Product $item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setOffer(WC_Order_Item_Product $item)
|
||||
{
|
||||
$uid = $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'] ;
|
||||
$offer = ['externalId' => $uid];
|
||||
|
||||
$product = $item->get_product();
|
||||
|
||||
if (
|
||||
!empty($product)
|
||||
&& isset($this->settings['bind_by_sku'])
|
||||
&& $this->settings['bind_by_sku'] == WC_Retailcrm_Base::YES
|
||||
) {
|
||||
$offer['xmlId'] = $product->get_sku();
|
||||
|
||||
unset($offer['externalId']);
|
||||
}
|
||||
|
||||
$this->setField('offer', $offer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order_Item_Product $item
|
||||
* @param int $decimalPlaces Price rounding from WC settings
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private function calculatePrice(WC_Order_Item_Product $item, int $decimalPlaces)
|
||||
{
|
||||
if (isset($this->settings['loyalty']) && $this->settings['loyalty'] === WC_Retailcrm_Base::YES) {
|
||||
$product = $item->get_product();
|
||||
$price = wc_get_price_including_tax($product, ["price" => $product->get_regular_price()]);
|
||||
} else {
|
||||
$price = ($item['line_subtotal'] / $item->get_quantity()) + ($item['line_subtotal_tax'] / $item->get_quantity());
|
||||
}
|
||||
|
||||
return round($price, $decimalPlaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order_Item_Product $item
|
||||
* @param $price
|
||||
* @param int $decimalPlaces Price rounding from WC settings
|
||||
* @param array|null $crmItem Current trade position in CRM
|
||||
* @return float|int
|
||||
*/
|
||||
private function calculateDiscount(
|
||||
WC_Order_Item_Product $item,
|
||||
$price,
|
||||
int $decimalPlaces,
|
||||
$crmItem = null
|
||||
) {
|
||||
|
||||
if ($crmItem && isset($this->settings['loyalty']) && $this->settings['loyalty'] === WC_Retailcrm_Base::YES) {
|
||||
$loyaltyDiscount = 0;
|
||||
|
||||
foreach ($crmItem['discounts'] as $discount) {
|
||||
if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) {
|
||||
$loyaltyDiscount += $discount['amount'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The loyalty program discount is calculated within the CRM system. It must be deleted during transfer to avoid duplication.
|
||||
*/
|
||||
$productPrice = ($item->get_total() / $item->get_quantity()) + ($loyaltyDiscount / $crmItem['quantity']);
|
||||
|
||||
if ($this->cancelLoyalty) {
|
||||
if ($item->get_total() + $loyaltyDiscount <= $item->get_subtotal()) {
|
||||
$item->set_total($item->get_total() + $loyaltyDiscount);
|
||||
$item->calculate_taxes();
|
||||
$item->save();
|
||||
}
|
||||
|
||||
$productPrice = $item->get_total() / $item->get_quantity();
|
||||
}
|
||||
} else {
|
||||
$productPrice = $item->get_total() ? $item->get_total() / $item->get_quantity() : 0;
|
||||
}
|
||||
|
||||
$productTax = $item->get_total_tax() ? $item->get_total_tax() / $item->get_quantity() : 0;
|
||||
$itemPrice = $productPrice + $productTax;
|
||||
|
||||
return round($price - $itemPrice, $decimalPlaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset item data.
|
||||
*/
|
||||
public function resetData($cancelLoyalty)
|
||||
{
|
||||
$this->data = [
|
||||
'offer' => [],
|
||||
'productName' => '',
|
||||
'initialPrice' => 0.00,
|
||||
'quantity' => 0.00
|
||||
];
|
||||
|
||||
$this->cancelLoyalty = $cancelLoyalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking whether the loyalty program discount needs to be canceled. (Changing the sales items in the order)
|
||||
*
|
||||
* @param array $wcItems
|
||||
* @param array $crmItems
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCancelLoyalty($wcItems, $crmItems): bool
|
||||
{
|
||||
/** If the number of sales items does not match */
|
||||
if (count($wcItems) !== count($crmItems)) {
|
||||
$this->cancelLoyalty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($wcItems as $id => $item) {
|
||||
$loyaltyDiscount = 0;
|
||||
|
||||
/** If a trading position has been added/deleted */
|
||||
if (!isset($crmItems[$id])) {
|
||||
$this->cancelLoyalty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** If the quantity of goods in a trade item does not match */
|
||||
if ($item->get_quantity() !== $crmItems[$id]['quantity']) {
|
||||
$this->cancelLoyalty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($crmItems[$id]['discounts'] as $discount) {
|
||||
if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) {
|
||||
$loyaltyDiscount = $discount['amount'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*If the sum of the trade item including discounts and loyalty program discount exceeds the cost without discounts.
|
||||
* (Occurs when recalculating an order, deleting/adding coupons)
|
||||
*/
|
||||
if (($item->get_total() + $loyaltyDiscount) > $item->get_subtotal()) {
|
||||
$this->cancelLoyalty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
134
src/include/order/class-wc-retailcrm-order-payment.php
Normal file
134
src/include/order/class-wc-retailcrm-order-payment.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Order_Payment - Build payments for CRM order.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Order_Payment extends WC_Retailcrm_Abstracts_Data
|
||||
{
|
||||
/** @var array */
|
||||
protected $data = [
|
||||
'type' => '',
|
||||
'order' => [],
|
||||
'externalId' => '',
|
||||
];
|
||||
|
||||
/** @var bool */
|
||||
public $isNew = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = [];
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Order_Item constructor.
|
||||
*
|
||||
* @param array $settings
|
||||
*/
|
||||
public function __construct($settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order $order
|
||||
* @param mixed $externalId
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function build($order, $externalId = false)
|
||||
{
|
||||
$this->resetData();
|
||||
|
||||
$paymentData = [];
|
||||
|
||||
if (!$this->isNew) {
|
||||
$paymentData['externalId'] = $externalId;
|
||||
} else {
|
||||
$paymentData['externalId'] = uniqid($order->get_id() . '-');
|
||||
}
|
||||
|
||||
$paymentData['order'] = [
|
||||
'externalId' => $order->get_id()
|
||||
];
|
||||
|
||||
if ($order->is_paid()) {
|
||||
if ($order->get_status() != 'completed' && $order->get_payment_method() == 'cod') {
|
||||
WC_Retailcrm_Logger::info(
|
||||
__METHOD__,
|
||||
sprintf('Payment for order: %s. ' .
|
||||
'Payment status cannot be changed as it is cash (or other payment method) on delivery. ' .
|
||||
'The status will be changed when the order is in status completed.',
|
||||
$order->get_id()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$paymentData['status'] = 'paid';
|
||||
|
||||
if (isset($this->settings[$order->get_payment_method()])) {
|
||||
$paymentData['type'] = $this->settings[$order->get_payment_method()];
|
||||
}
|
||||
|
||||
$paidAt = $order->get_date_paid();
|
||||
|
||||
if (!empty($paidAt)) {
|
||||
$paymentData['paidAt'] = $paidAt->date('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isNew) {
|
||||
if (isset($this->settings[$order->get_payment_method()])) {
|
||||
$paymentData['type'] = $this->settings[$order->get_payment_method()];
|
||||
} else {
|
||||
$paymentData = [];
|
||||
}
|
||||
}
|
||||
|
||||
$paymentData = apply_filters(
|
||||
'retailcrm_process_payment',
|
||||
WC_Retailcrm_Plugin::clearArray($paymentData),
|
||||
$order
|
||||
);
|
||||
|
||||
$this->setDataFields($paymentData);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if payment doesn't have method
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
$data = parent::getData();
|
||||
|
||||
if (empty($data['type'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Need to clear the array from empty values
|
||||
return array_filter($data);
|
||||
}
|
||||
|
||||
public function resetData()
|
||||
{
|
||||
$this->data = [
|
||||
'externalId' => '',
|
||||
'type' => '',
|
||||
'status' => '',
|
||||
'paidAt' => '',
|
||||
'order' => []
|
||||
];
|
||||
}
|
||||
}
|
103
src/include/order/class-wc-retailcrm-order.php
Normal file
103
src/include/order/class-wc-retailcrm-order.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.0
|
||||
*
|
||||
* Class WC_Retailcrm_Order - Build main data for CRM order.
|
||||
*
|
||||
* @category Integration
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license http://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://help.retailcrm.ru
|
||||
*/
|
||||
class WC_Retailcrm_Order extends WC_Retailcrm_Abstracts_Data
|
||||
{
|
||||
/** @var bool */
|
||||
public $is_new = true;
|
||||
|
||||
protected $data = [
|
||||
'externalId' => 0,
|
||||
'status' => '',
|
||||
'number' => '',
|
||||
'createdAt' => '',
|
||||
'firstName' => '',
|
||||
'lastName' => '',
|
||||
'email' => '',
|
||||
'paymentType' => '',
|
||||
'customerComment' => '',
|
||||
'paymentStatus' => '',
|
||||
'phone' => '',
|
||||
'countryIso' => ''
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = [];
|
||||
|
||||
/**
|
||||
* WC_Retailcrm_Order constructor.
|
||||
*
|
||||
* @param array $settings
|
||||
*/
|
||||
public function __construct($settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function build($order)
|
||||
{
|
||||
$firstName = $order->get_shipping_first_name();
|
||||
$lastName = $order->get_shipping_last_name();
|
||||
|
||||
if (empty($firstName) && empty($lastName)) {
|
||||
$firstName = $order->get_billing_first_name();
|
||||
$lastName = $order->get_billing_last_name();
|
||||
}
|
||||
|
||||
$dateCreate = $order->get_date_created();
|
||||
|
||||
$data = [
|
||||
'externalId' => $order->get_id(),
|
||||
'createdAt' => !empty($dateCreate) ? $dateCreate->date('Y-m-d H:i:s') : date('Y-m-d H:i:s'),
|
||||
'firstName' => $firstName,
|
||||
'lastName' => $lastName,
|
||||
'email' => strtolower($order->get_billing_email()),
|
||||
'customerComment' => $order->get_customer_note(),
|
||||
'phone' => $order->get_billing_phone(),
|
||||
'countryIso' => $order->get_shipping_country()
|
||||
];
|
||||
|
||||
if ($data['countryIso'] == '--' || empty($data['countryIso'])) {
|
||||
$countries = new WC_Countries();
|
||||
$data['countryIso'] = $countries->get_base_country();
|
||||
}
|
||||
|
||||
$this->setDataFields($data);
|
||||
$this->setNumber($order);
|
||||
|
||||
if (isset($this->settings[$order->get_status()]) && 'not-upload' !== $this->settings[$order->get_status()]) {
|
||||
$this->setField('status', $this->settings[$order->get_status()]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WC_Order $order
|
||||
*/
|
||||
protected function setNumber($order)
|
||||
{
|
||||
if (isset($this->settings['update_number']) && $this->settings['update_number'] == WC_Retailcrm_Base::YES) {
|
||||
$this->setField('number', $order->get_order_number());
|
||||
} else {
|
||||
unset($this->data['number']);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue