complete structure revamp

This commit is contained in:
2025-10-07 14:46:31 +03:00
parent 0d800e014e
commit 1081f2cca6
11 changed files with 1009 additions and 377 deletions

371
README.md
View File

@@ -5,165 +5,318 @@ Go SDK для работы с API кассы ВЧАСНО - украинской
## Установка ## Установка
```bash ```bash
go get git.jeezft.xyz/rk/go-vchasno-kassa go get gitea.jeezft.xyz/jeezft/go-vchasno-kassa
``` ```
## Быстрый старт ## Быстрый старт
### Базовое использование
```go ```go
package main package main
import ( import (
"context" "context"
"log" "log"
"time"
"git.jeezft.xyz/rk/go-vchasno-kassa" "gitea.jeezft.xyz/jeezft/go-vchasno-kassa"
) )
func main() { func main() {
client := vchasno.NewClient(vchasno.Config{ client := vchasno.NewClient(vchasno.Config{
Token: "your-api-token-here", Token: "your-api-token-here",
Timeout: 30 * time.Second,
}) })
ctx := context.Background() ctx := context.Background()
// Проверка подключения response, err := client.QuickSell(ctx, 100.00)
if err := client.Ping(ctx); err != nil {
log.Fatal(err)
}
// Создание фискального чека
receipt := vchasno.FiscalReceipt{
Cashier: "Иванов И.И.",
CashierTaxID: "1234567890",
Items: []vchasno.ReceiptItem{
{
Name: "Parking on obj1",
Code: "PARK001",
Price: vchasno.NewMoney(10.00),
Quantity: 2.0,
Amount: vchasno.NewMoney(20.00),
Tax: vchasno.NewTax(4.0, vchasno.NewMoney(0.80)),
},
},
Payments: []vchasno.Payment{
{
Type: "cash",
Amount: vchasno.NewMoney(20.00),
},
},
Total: vchasno.NewMoney(20.00),
TaxTotal: vchasno.NewMoney(0.80),
}
response, err := client.CreateFiscalReceipt(ctx, receipt)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Printf("Fiscal receipt created: %s", response.FiscalNumber) log.Printf("Sale created: %s", response.Info.Doccode)
} }
``` ```
### С дефолтными параметрами
```go
client := vchasno.NewClient(vchasno.Config{
Token: "your-api-token-here",
Cashier: "Иванов",
Source: "parking",
Defaults: &vchasno.DefaultParams{
ProductName: "Парковка",
Comment: "Оплата парковки",
Taxgrp: "1",
PayType: api.PayTypeCash,
DefaultTimeout: 30 * time.Second,
},
})
response, _ := client.QuickSell(ctx, 50.00)
```
### С Builder Pattern
```go
response, err := client.NewSellParams().
Name("Парковка VIP").
Price(150.00).
Cnt(2).
Comment("2 часа").
PayCash().
ExecuteDefault()
```
## Функциональность ## Функциональность
### Работа с фискальными чеками ### Работа со сменами
- Создание фискальных чеков через единый API эндпоинт `/api/v3/fiscal/execute` - Открытие смены (`OpenShift`)
- Получение информации о фискализованном чеке - Закрытие смены с Z-отчетом (`CloseShift`)
- Отмена фискальных чеков
- Поддержка различных типов оплаты (наличные, карта)
- Автоматический расчет НДС и налогов
### Отчеты ### Продажи
- X-отчет (промежуточный отчет без обнуления) - Создание чеков с оплатой наличными
- Z-отчет (итоговый отчет с обнулением кассы) - Создание чеков с оплатой картой
- Поддержка дополнительных данных клиента
- Автоматический расчет сумм
### Валидация данных ### Z-отчет содержит
- Проверка корректности сумм и количества - Количество чеков (продажа/возврат)
- Валидация обязательных полей - Сводку по налогам
- Проверка соответствия общей суммы и оплат - Информацию о платежах
- Остатки в кассе
- Детальную информацию по налоговым группам
## Структура проекта
```
api/
├── client.go - Клиент API
├── constants.go - Константы (типы задач, платежей)
├── requests.go - Структуры запросов
├── responses.go - Структуры ответов
├── helpers.go - Вспомогательные функции
└── fiscal.go - Методы API
```
## Примеры использования ## Примеры использования
Полные примеры использования находятся в папке `examples/`. ### 1. Быстрая продажа (QuickSell)
```bash
cd examples
go run main.go
```
## Валидация
SDK включает встроенную валидацию фискальных чеков:
```go ```go
receipt := vchasno.FiscalReceipt{ response, err := client.QuickSell(ctx, 100.00)
Cashier: "Иванов И.И.",
CashierTaxID: "1234567890",
Items: []vchasno.ReceiptItem{...},
Payments: []vchasno.Payment{...},
Total: vchasno.NewMoney(100.00),
TaxTotal: vchasno.NewMoney(4.00),
}
if err := receipt.Validate(); err != nil {
log.Fatal("Validation error:", err)
}
``` ```
## Обработка ошибок Использует все дефолтные параметры из конфигурации.
### 2. Быстрая продажа с названием
```go ```go
response, err := client.CreateFiscalReceipt(ctx, receipt) response, err := client.QuickSellNamed(ctx, "Парковка VIP", 150.00)
if err != nil {
switch {
case errors.Is(err, vchasno.ErrMissingToken):
log.Println("Отсутствует токен авторизации")
default:
log.Printf("API error: %v", err)
}
}
``` ```
## Работа с деньгами ### 3. Builder Pattern - базовый пример
SDK использует копейки для точных денежных расчетов:
```go ```go
// Создание суммы в копейках response, err := client.NewSellParams().
price := vchasno.NewMoney(10.50) // 1050 копеек Price(100.00).
ExecuteDefault()
// Конвертация обратно в гривны
amount := price.ToFloat64() // 10.50
// Создание налога
tax := vchasno.NewTax(4.0, vchasno.NewMoney(0.42)) // 4% НДС
``` ```
## Типы операций ### 4. Builder Pattern - полный пример
Через эндпоинт `/api/v3/fiscal/execute` выполняются следующие команды:
- `create_receipt` - создание фискального чека
- `get_receipt` - получение информации о чеке
- `cancel_receipt` - отмена чека
- `x_report` - получение X-отчета
- `z_report` - получение Z-отчета
- `ping` - проверка соединения
## Конфигурация
```go ```go
client := vchasno.NewClient(vchasno.Config{ response, err := client.NewSellParams().
BaseURL: "https://kasa.vchasno.ua", // необязательно, по умолчанию Name("Парковка premium").
Token: "your-token", // обязательно Price(200.00).
Timeout: 30 * time.Second, // необязательно Cnt(2).
HTTPClient: &http.Client{...}, // необязательно Disc(20.00).
Comment("Скидка 10%").
Taxgrp("1").
PayCash().
ExecuteDefault()
```
### 5. Оплата картой через Builder
```go
response, err := client.NewSellParams().
Name("Услуга парковки").
Price(150.00).
PayCard("411111****1111", "305299", "123456789012", "123456").
Userinfo("user@example.com", "+380501234567").
ExecuteDefault()
```
### 6. Традиционный способ (без Builder)
```go
response, err := client.Sell(ctx, vchasno.SellParams{
Name: "Товар",
Cnt: 2,
Price: 50.00,
Taxgrp: "1",
PayType: api.PayTypeCash,
}) })
``` ```
### 7. Полный рабочий цикл
```go
client := vchasno.NewClient(vchasno.Config{
Token: "your-token",
Defaults: &vchasno.DefaultParams{
ProductName: "Парковка",
Taxgrp: "1",
},
})
ctx := context.Background()
client.OpenShift(ctx)
client.NewSellParams().Price(50.00).PayCash().ExecuteDefault()
client.NewSellParams().Price(100.00).PayCash().ExecuteDefault()
client.NewSellParams().Price(75.00).PayCard("411111****1111", "305299", "123456789012", "123456").ExecuteDefault()
zReport, _ := client.CloseShift(ctx)
fmt.Printf("Итого за смену: %.2f\n", zReport.Info.Safe)
```
### 8. Изменение дефолтов в процессе работы
```go
client.SetDefaults(vchasno.DefaultParams{
ProductName: "VIP Парковка",
Comment: "VIP зона",
Taxgrp: "2",
PayType: api.PayTypeCard,
DefaultTimeout: 60 * time.Second,
})
response, _ := client.QuickSell(ctx, 200.00)
```
### 9. Множественные продажи
```go
prices := []float64{50.00, 75.00, 100.00, 125.00}
for _, price := range prices {
client.NewSellParams().
Price(price).
PayCash().
ExecuteDefault()
}
```
### 10. Собственный таймаут
```go
response, err := client.NewSellParams().
Price(100.00).
ExecuteWithTimeout(45 * time.Second)
```
## Хелперы и удобные функции
### DefaultParams - дефолтные параметры
При создании клиента можно задать дефолтные значения, которые будут использоваться автоматически:
```go
client := vchasno.NewClient(vchasno.Config{
Token: "your-token",
Defaults: &vchasno.DefaultParams{
ProductName: "Парковка",
Comment: "Оплата услуг",
Taxgrp: "1",
PayType: api.PayTypeCash,
DefaultTimeout: 30 * time.Second,
},
})
```
### Builder Pattern
Builder Pattern позволяет строить параметры продажи в цепочке вызовов:
```go
client.NewSellParams().
Name("Товар").
Price(100.00).
Cnt(2).
Disc(10.00).
Comment("Комментарий").
Taxgrp("1").
PayCash().
ExecuteDefault()
```
Доступные методы Builder:
- `Name(string)` - название товара
- `Price(float64)` - цена
- `Cnt(int)` - количество
- `Disc(float64)` - скидка
- `Comment(string)` - комментарий
- `Taxgrp(string)` - налоговая группа
- `PayCash()` - оплата наличными
- `PayCard(cardmask, bankID, rrnCode, authCode)` - оплата картой
- `Userinfo(email, phone)` - данные клиента
- `Build()` - получить SellParams
- `Execute(ctx)` - выполнить с контекстом
- `ExecuteWithTimeout(duration)` - выполнить с таймаутом
- `ExecuteDefault()` - выполнить с дефолтным таймаутом
### QuickSell методы
Для быстрых продаж:
```go
client.QuickSell(ctx, 100.00)
client.QuickSellNamed(ctx, "Парковка", 100.00)
```
### Изменение дефолтов
Дефолтные параметры можно изменить в любой момент:
```go
client.SetDefaults(vchasno.DefaultParams{
ProductName: "Новое название",
PayType: api.PayTypeCard,
})
defaults := client.GetDefaults()
```
## Константы
### Типы задач
- `api.TaskOpenShift = 0` - Открытие смены
- `api.TaskSell = 1` - Продажа
- `api.TaskZReport = 11` - Z-отчет
### Типы платежей
- `api.PayTypeCash = 0` - Оплата наличными
- `api.PayTypeCard = 2` - Оплата картой
## Структуры ответов
### SellResponse
Используется для продаж и открытия смены. Содержит базовую информацию о документе.
### ZReportResponse
Используется для закрытия смены. Содержит детальную информацию:
- `Receipt` - статистика по чекам
- `Summary` - итоговые суммы
- `Taxes` - разбивка по налогам
- `Pays` - способы оплаты
- `Money` - движение наличных
- `Cash` - остатки по безналичным
## API Reference ## API Reference
Полная документация API ВЧАСНО доступна по адресу: https://documenter.getpostman.com/view/26351974/2s93shy9To Полная документация API ВЧАСНО: https://documenter.getpostman.com/view/26351974/2s93shy9To

17
api/client.go Normal file
View File

@@ -0,0 +1,17 @@
package api
import "resty.dev/v3"
type Client struct {
token string
resty *resty.Client
apiBaseURL string
}
func NewClient(token string) *Client {
return &Client{
token: token,
resty: resty.New(),
apiBaseURL: "https://kasa.vchasno.ua/api/v3",
}
}

16
api/constants.go Normal file
View File

@@ -0,0 +1,16 @@
package api
const (
TaskOpenShift = 0
TaskSell = 1
TaskZReport = 11
)
const (
PayTypeCash = 0
PayTypeCard = 2
)
const (
PaySystemParkingPos = "parking_pos"
)

103
api/fiscal.go Normal file
View File

@@ -0,0 +1,103 @@
package api
import (
"context"
"encoding/json"
"fmt"
"io"
)
func (c *Client) executeRequest(ctx context.Context, request FiscalRequest, response interface{}) error {
resp, err := c.resty.R().
SetContext(ctx).
SetHeader("Authorization", c.token).
SetBody(request).
Post(c.apiBaseURL + "/fiscal/execute")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
if resp.IsError() {
return fmt.Errorf("api error: %v", resp.Error())
}
if resp.StatusCode() != 200 {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode())
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
if err := json.Unmarshal(body, response); err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
return nil
}
func (c *Client) OpenShift(ctx context.Context, cashier string) (*SellResponse, error) {
request := FiscalRequest{
Fiscal: Fiscal{
Task: TaskOpenShift,
Cashier: cashier,
},
}
var response SellResponse
if err := c.executeRequest(ctx, request, &response); err != nil {
return nil, err
}
return &response, nil
}
func (c *Client) CloseShift(ctx context.Context, cashier string) (*ZReportResponse, error) {
request := FiscalRequest{
Fiscal: Fiscal{
Task: TaskZReport,
Cashier: cashier,
},
}
var response ZReportResponse
if err := c.executeRequest(ctx, request, &response); err != nil {
return nil, err
}
return &response, nil
}
type SellParams struct {
Cashier string
Source string
Rows []ReceiptRow
Pays []ReceiptPay
Userinfo *Userinfo
}
func (c *Client) Sell(ctx context.Context, params SellParams) (*SellResponse, error) {
receipt := NewReceipt(params.Rows, params.Pays)
request := FiscalRequest{
Source: params.Source,
Fiscal: Fiscal{
Task: TaskSell,
Cashier: params.Cashier,
Receipt: &receipt,
},
}
if params.Userinfo != nil {
request.Userinfo = *params.Userinfo
}
var response SellResponse
if err := c.executeRequest(ctx, request, &response); err != nil {
return nil, err
}
return &response, nil
}

49
api/helpers.go Normal file
View File

@@ -0,0 +1,49 @@
package api
func NewReceiptRow(name string, cnt int, price float64, taxgrp string) ReceiptRow {
return ReceiptRow{
Name: name,
Cnt: cnt,
Price: price,
Taxgrp: taxgrp,
}
}
func NewReceiptPayCash(sum float64, comment string) ReceiptPay {
return ReceiptPay{
Type: PayTypeCash,
Sum: sum,
Comment: comment,
}
}
func NewReceiptPayCard(sum float64, cardmask, bankID, rrnCode, authCode string) ReceiptPay {
return ReceiptPay{
Type: PayTypeCard,
Sum: sum,
Paysys: PaySystemParkingPos,
Cardmask: cardmask,
BankID: bankID,
Rrn: rrnCode,
AuthCode: authCode,
}
}
func CalculateReceiptSum(rows []ReceiptRow) float64 {
sum := 0.0
for _, row := range rows {
sum += (row.Price - row.Disc) * float64(row.Cnt)
}
return sum
}
func NewReceipt(rows []ReceiptRow, pays []ReceiptPay) Receipt {
return Receipt{
Sum: CalculateReceiptSum(rows),
Round: 0.00,
Disc: 0,
DiscType: 0,
Rows: rows,
Pays: pays,
}
}

View File

@@ -1,152 +0,0 @@
package api
import (
"context"
"encoding/json"
"fmt"
"io"
"resty.dev/v3"
)
func NewKasaInstance(token string) *Kasa {
return &Kasa{
token: token,
resty: resty.New(),
}
}
type Kasa struct {
token string
resty *resty.Client
}
func createReceiptRow(name string, cnt int, price float64, comment string, disc float64, taxgrp string) ReceiptRow {
return ReceiptRow{
Name: name,
Cnt: cnt,
Price: price,
Disc: disc,
Taxgrp: taxgrp,
Comment: comment,
}
}
func createReceiptPayCash(PayType int, sum float64, comment string) ReceiptPay {
return ReceiptPay{
Type: PayType,
Sum: sum,
Comment: comment,
}
}
func createReceiptPayCard(PayType int, sum float64, comment string, cardmask string, bankID string, rrnCode string, authCode string) ReceiptPay {
return ReceiptPay{
Type: PayType,
Sum: sum,
Comment: comment,
Paysys: "parking_pos",
Cardmask: cardmask,
BankID: bankID,
Rrn: rrnCode,
AuthCode: authCode,
}
}
func getReceiptSum(rows []ReceiptRow, taxgrp string) float64 {
sum := 0.0
for _, row := range rows {
sum += (row.Price - row.Disc) * float64(row.Cnt)
}
return sum
}
func createReceipt(PayType int, sum float64, comment string, cardmask string, bankID string, name string, cnt int, price float64, disc float64, taxgrp string, rrnCode string, authCode string) Receipt {
rows := []ReceiptRow{createReceiptRow(name, cnt, price, comment, disc, taxgrp)}
sum = getReceiptSum(rows, taxgrp)
r := Receipt{
Sum: sum,
Round: 0.00,
Disc: 0,
DiscType: 0,
Rows: rows,
Pays: []ReceiptPay{createReceiptPayCard(PayType, sum, comment, cardmask, bankID, rrnCode, authCode)},
}
fmt.Println("Receipt sum:", r.Sum)
return r
}
// "fiscal": {
// "task": 1,
// "cashier": "Парковка",
// "receipt": {
// "sum": 40.00,
// "round": 0.00,
// "comment_up": "Квитанція за паркування",
// "comment_down": "Дякуємо за користування паркінгом!",
// "disc": 0.00,
// "disc_type": 0,
// "rows": [
// {
// "code": "PARK-3H",
// "pop": "Оплата за послуги паркування",
// "name": "Парковка, 3 години",
// "cnt": 1,
// "price": 40.00,
// "disc": 0.00,
// "taxgrp": "4",
// "comment": "Тариф: 3 години"
// }
// ],
// "pays": [
// {
// "type": 0,
// "sum": 40.00,
// "change": 0.00,
// "comment": "Оплата готівкою"
// }
// ]
// }
// }
func createFiscal(source string, cashier string, PayType int, sum float64, comment string, cardmask string, bankID string, name string, cnt int, price float64, disc float64, taxgrp string, rrnCode string, authCode string) FiskalCheck {
return FiskalCheck{
Source: source,
Fiscal: Fiscal{
Task: 1,
Cashier: cashier,
Receipt: createReceipt(PayType, sum, comment, cardmask, bankID, name, cnt, price, disc, taxgrp, rrnCode, authCode),
},
}
}
func (k *Kasa) NewSell(ctx context.Context, PayType int, sum float64, comment string, cardmask string, bankID string, name string, cnt int, price float64, disc float64, taxgrp string, rrnCode string, authCode string) (*KasaResponse, error) {
fiscal := createFiscal("kasa", "test", PayType, sum, comment, cardmask, bankID, name, cnt, price, disc, taxgrp, rrnCode, authCode)
// create a POST request to the kasa api https://kasa.vchasno.ua/api/v3/fiscal/execute with resty
request, err := k.resty.R().SetBody(fiscal).SetHeader("Authorization", k.token).Post("https://kasa.vchasno.ua/api/v3/fiscal/execute")
if err != nil {
return nil, fmt.Errorf("failed to create a POST request to the kasa api: %w", err)
}
if request.IsError() {
return nil, fmt.Errorf("failed to create a POST request to the kasa api: %v", request.Error())
}
if request.StatusCode() != 200 {
return nil, fmt.Errorf("failed to create a POST request to the kasa api: %v", request.Error())
}
body, err := io.ReadAll(request.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
fmt.Println(string(body))
var response KasaResponse
if err := json.Unmarshal(body, &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &response, nil
}

61
api/requests.go Normal file
View File

@@ -0,0 +1,61 @@
package api
type FiscalRequest struct {
Source string `json:"source"`
Userinfo Userinfo `json:"userinfo,omitempty"`
Fiscal Fiscal `json:"fiscal"`
}
type Userinfo struct {
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
}
type Fiscal struct {
Task int `json:"task"`
Cashier string `json:"cashier"`
Receipt *Receipt `json:"receipt,omitempty"`
}
type Receipt struct {
Sum float64 `json:"sum"`
Round float64 `json:"round"`
CommentUp string `json:"comment_up,omitempty"`
CommentDown string `json:"comment_down,omitempty"`
Disc float64 `json:"disc"`
DiscType int `json:"disc_type"`
Rows []ReceiptRow `json:"rows"`
Pays []ReceiptPay `json:"pays"`
}
type ReceiptRow struct {
Code string `json:"code,omitempty"`
Pop string `json:"pop,omitempty"`
Code1 string `json:"code1,omitempty"`
Code2 string `json:"code2,omitempty"`
CodeAa []string `json:"code_aa,omitempty"`
Name string `json:"name"`
Cnt int `json:"cnt"`
Price float64 `json:"price"`
Disc float64 `json:"disc"`
Taxgrp string `json:"taxgrp"`
Comment string `json:"comment,omitempty"`
CodeA string `json:"code_a,omitempty"`
}
type ReceiptPay struct {
Type int `json:"type"`
Sum float64 `json:"sum"`
Change float64 `json:"change,omitempty"`
Comment string `json:"comment,omitempty"`
Commission float64 `json:"commission,omitempty"`
Paysys string `json:"paysys,omitempty"`
Rrn string `json:"rrn,omitempty"`
OperType string `json:"oper_type,omitempty"`
Cardmask string `json:"cardmask,omitempty"`
TermID string `json:"term_id,omitempty"`
BankName string `json:"bank_name,omitempty"`
BankID string `json:"bank_id,omitempty"`
AuthCode string `json:"auth_code,omitempty"`
ShowAdditionalInfo bool `json:"show_additional_info,omitempty"`
}

124
api/responses.go Normal file
View File

@@ -0,0 +1,124 @@
package api
type BaseResponse struct {
Task int `json:"task"`
Type int `json:"type"`
Ver int `json:"ver"`
Source string `json:"source"`
Device string `json:"device"`
Tag string `json:"tag"`
Dt string `json:"dt"`
Res int `json:"res"`
ResAction int `json:"res_action"`
Errortxt string `json:"errortxt"`
Warnings []string `json:"warnings"`
ErrorExtra interface{} `json:"error_extra"`
}
type SellResponse struct {
BaseResponse
Info SellInfo `json:"info"`
}
type SellInfo struct {
Task int `json:"task"`
Fisid string `json:"fisid"`
Dataid int `json:"dataid"`
Doccode string `json:"doccode"`
Dt string `json:"dt"`
Cashier string `json:"cashier"`
Dtype int `json:"dtype"`
Isprint int `json:"isprint"`
Isoffline bool `json:"isoffline"`
Safe float64 `json:"safe"`
ShiftLink int `json:"shift_link"`
Docno int `json:"docno"`
Cancelid string `json:"cancelid,omitempty"`
}
type ZReportResponse struct {
BaseResponse
Info ZReportInfo `json:"info"`
}
type ZReportInfo struct {
Task int `json:"task"`
Fisid string `json:"fisid"`
Dataid int `json:"dataid"`
Doccode string `json:"doccode"`
Dt string `json:"dt"`
Cashier string `json:"cashier"`
Dtype int `json:"dtype"`
Isprint int `json:"isprint"`
Isoffline bool `json:"isoffline"`
Safe float64 `json:"safe"`
ShiftLink int `json:"shift_link"`
Docno int `json:"docno"`
Receipt ZReportReceipt `json:"receipt"`
Summary ZReportSummary `json:"summary"`
Taxes []ZReportTax `json:"taxes"`
Pays []ZReportPay `json:"pays"`
Money []ZReportMoney `json:"money"`
Cash []ZReportMoney `json:"cash"`
MoneyTransfer []interface{} `json:"money_transfer"`
}
type ZReportReceipt struct {
CountP int `json:"count_p"`
CountM int `json:"count_m"`
Count14 int `json:"count_14"`
CountTransfer int `json:"count_transfer"`
LastDocnoP int `json:"last_docno_p"`
LastDocnoM int `json:"last_docno_m"`
}
type ZReportSummary struct {
BaseP float64 `json:"base_p"`
BaseM float64 `json:"base_m"`
TaxexP float64 `json:"taxex_p"`
TaxexM float64 `json:"taxex_m"`
DiscP float64 `json:"disc_p"`
DiscM float64 `json:"disc_m"`
}
type ZReportTax struct {
GrCode int `json:"gr_code"`
BaseSumP float64 `json:"base_sum_p"`
BaseSumM float64 `json:"base_sum_m"`
BaseTaxSumP float64 `json:"base_tax_sum_p"`
BaseTaxSumM float64 `json:"base_tax_sum_m"`
BaseExSumP float64 `json:"base_ex_sum_p"`
BaseExSumM float64 `json:"base_ex_sum_m"`
TaxName string `json:"tax_name"`
TaxFname string `json:"tax_fname"`
TaxLit string `json:"tax_lit"`
TaxPercent float64 `json:"tax_percent"`
TaxSumP float64 `json:"tax_sum_p"`
TaxSumM float64 `json:"tax_sum_m"`
ExName string `json:"ex_name"`
ExPercent float64 `json:"ex_percent"`
ExSumP float64 `json:"ex_sum_p"`
ExSumM float64 `json:"ex_sum_m"`
}
type ZReportPay struct {
Type int `json:"type"`
Name string `json:"name"`
SumP float64 `json:"sum_p"`
SumM float64 `json:"sum_m"`
RoundPu float64 `json:"round_pu"`
RoundPd float64 `json:"round_pd"`
RoundMu float64 `json:"round_mu"`
RoundMd float64 `json:"round_md"`
}
type ZReportMoney struct {
Type int `json:"type"`
Name string `json:"name"`
SumP float64 `json:"sum_p"`
SumM float64 `json:"sum_m"`
RoundPu float64 `json:"round_pu"`
RoundPd float64 `json:"round_pd"`
RoundMu float64 `json:"round_mu"`
RoundMd float64 `json:"round_md"`
}

View File

@@ -1,93 +0,0 @@
package api
type FiskalCheck struct {
Source string `json:"source"`
Userinfo Userinfo `json:"userinfo"`
Fiscal Fiscal `json:"fiscal"`
}
type Userinfo struct {
Email string `json:"email"`
Phone string `json:"phone"`
}
type Fiscal struct {
Task int `json:"task"`
Cashier string `json:"cashier"`
Receipt Receipt `json:"receipt"`
}
type Receipt struct {
Sum float64 `json:"sum"`
Round float64 `json:"round"`
CommentUp string `json:"comment_up"`
CommentDown string `json:"comment_down"`
Disc float64 `json:"disc"`
DiscType int `json:"disc_type"`
Rows []ReceiptRow `json:"rows"`
Pays []ReceiptPay `json:"pays"`
}
type ReceiptRow struct {
Code string `json:"code"`
Pop string `json:"pop,omitempty"`
Code1 string `json:"code1"`
Code2 string `json:"code2"`
CodeAa []string `json:"code_aa,omitempty"`
Name string `json:"name"`
Cnt int `json:"cnt"`
Price float64 `json:"price"`
Disc float64 `json:"disc"`
Taxgrp string `json:"taxgrp"`
Comment string `json:"comment"`
CodeA string `json:"code_a,omitempty"`
}
type ReceiptPay struct {
Type int `json:"type"`
Sum float64 `json:"sum"`
Change float64 `json:"change,omitempty"`
Comment string `json:"comment"`
Commission float64 `json:"commission,omitempty"`
Paysys string `json:"paysys,omitempty"`
Rrn string `json:"rrn,omitempty"`
OperType string `json:"oper_type,omitempty"`
Cardmask string `json:"cardmask,omitempty"`
TermID string `json:"term_id,omitempty"`
BankName string `json:"bank_name,omitempty"`
BankID string `json:"bank_id,omitempty"`
AuthCode string `json:"auth_code,omitempty"`
ShowAdditionalInfo bool `json:"show_additional_info,omitempty"`
}
type KasaResponse struct {
Task int `json:"task"`
Type int `json:"type"`
Ver int `json:"ver"`
Source string `json:"source"`
Device string `json:"device"`
Tag string `json:"tag"`
Dt string `json:"dt"`
Res int `json:"res"`
ResAction int `json:"res_action"`
Errortxt string `json:"errortxt"`
Warnings []string `json:"warnings"`
Info Info `json:"info"`
ErrorExtra interface{} `json:"error_extra"`
}
type Info struct {
Task int `json:"task"`
Fisid string `json:"fisid"`
Dataid int `json:"dataid"`
Doccode string `json:"doccode"`
Dt string `json:"dt"`
Cashier string `json:"cashier"`
Dtype int `json:"dtype"`
Isprint int `json:"isprint"`
Isoffline bool `json:"isoffline"`
Safe float64 `json:"safe"`
ShiftLink int `json:"shift_link"`
Docno int `json:"docno"`
Cancelid string `json:"cancelid"`
}

158
examples.go Normal file
View File

@@ -0,0 +1,158 @@
package vchasno
import (
"context"
"time"
"gitea.jeezft.xyz/jeezft/go-vchasno-kassa/api"
)
func ExampleBasicUsage() {
client := NewClient(Config{
Token: "your-token",
Cashier: "Иванов",
Source: "parking",
})
ctx := context.Background()
response, _ := client.QuickSell(ctx, 100.00)
_ = response
}
func ExampleWithDefaults() {
client := NewClient(Config{
Token: "your-token",
Cashier: "Иванов",
Defaults: &DefaultParams{
ProductName: "Парковка",
Comment: "Оплата парковки",
Taxgrp: "1",
PayType: api.PayTypeCash,
DefaultTimeout: 30 * time.Second,
},
})
ctx := context.Background()
response, _ := client.QuickSell(ctx, 50.00)
_ = response
}
func ExampleBuilderPattern() {
client := NewClient(Config{
Token: "your-token",
Cashier: "Иванов",
Defaults: &DefaultParams{
ProductName: "Парковка",
Taxgrp: "1",
PayType: api.PayTypeCash,
},
})
response, _ := client.NewSellParams().
Price(100.00).
Cnt(2).
Comment("Парковка на 2 часа").
ExecuteDefault()
_ = response
}
func ExampleBuilderWithCard() {
client := NewClient(Config{
Token: "your-token",
})
response, _ := client.NewSellParams().
Name("Услуга парковки").
Price(150.00).
PayCard("411111****1111", "305299", "123456789012", "123456").
Userinfo("user@example.com", "+380501234567").
ExecuteDefault()
_ = response
}
func ExampleChangingDefaults() {
client := NewClient(Config{
Token: "your-token",
})
client.SetDefaults(DefaultParams{
ProductName: "VIP Парковка",
Comment: "VIP зона",
Taxgrp: "2",
PayType: api.PayTypeCard,
DefaultTimeout: 60 * time.Second,
})
ctx := context.Background()
response, _ := client.QuickSell(ctx, 200.00)
_ = response
}
func ExampleFullWorkflow() {
client := NewClient(Config{
Token: "your-token",
Cashier: "Петров",
Defaults: &DefaultParams{
ProductName: "Парковка",
Taxgrp: "1",
},
})
ctx := context.Background()
openResp, _ := client.OpenShift(ctx)
_ = openResp
client.NewSellParams().
Price(50.00).
PayCash().
ExecuteDefault()
client.NewSellParams().
Price(100.00).
Cnt(2).
PayCard("411111****1111", "305299", "123456789012", "123456").
ExecuteDefault()
zReport, _ := client.CloseShift(ctx)
_ = zReport
}
func ExampleMultipleSales() {
client := NewClient(Config{
Token: "your-token",
Defaults: &DefaultParams{
ProductName: "Парковка",
Taxgrp: "1",
},
})
prices := []float64{50.00, 75.00, 100.00, 125.00}
for _, price := range prices {
client.NewSellParams().
Price(price).
PayCash().
ExecuteDefault()
}
}
func ExampleWithDiscount() {
client := NewClient(Config{
Token: "your-token",
})
response, _ := client.NewSellParams().
Name("Парковка premium").
Price(200.00).
Disc(20.00).
Comment("Скидка 10%").
PayCash().
ExecuteDefault()
_ = response
}

View File

@@ -7,51 +7,247 @@ import (
"gitea.jeezft.xyz/jeezft/go-vchasno-kassa/api" "gitea.jeezft.xyz/jeezft/go-vchasno-kassa/api"
) )
type Vchasno struct { type Client struct {
api *api.Kasa api *api.Client
cashier string
source string
defaults *DefaultParams
} }
const ( type Config struct {
PayTypeCash = 0 Token string
PayTypeCard = 2 Cashier string
) Source string
Defaults *DefaultParams
}
type DefaultParams struct {
ProductName string
Comment string
Taxgrp string
PayType int
DefaultTimeout time.Duration
}
func NewClient(config Config) *Client {
if config.Cashier == "" {
config.Cashier = "cashier"
}
if config.Source == "" {
config.Source = "api"
}
defaults := config.Defaults
if defaults == nil {
defaults = &DefaultParams{
Taxgrp: "1",
PayType: api.PayTypeCash,
DefaultTimeout: 30 * time.Second,
}
}
if defaults.Taxgrp == "" {
defaults.Taxgrp = "1"
}
if defaults.DefaultTimeout == 0 {
defaults.DefaultTimeout = 30 * time.Second
}
return &Client{
api: api.NewClient(config.Token),
cashier: config.Cashier,
source: config.Source,
defaults: defaults,
}
}
func (c *Client) SetDefaults(defaults DefaultParams) {
c.defaults = &defaults
}
func (c *Client) GetDefaults() DefaultParams {
return *c.defaults
}
func (c *Client) OpenShift(ctx context.Context) (*api.SellResponse, error) {
return c.api.OpenShift(ctx, c.cashier)
}
func (c *Client) CloseShift(ctx context.Context) (*api.ZReportResponse, error) {
return c.api.CloseShift(ctx, c.cashier)
}
type SellParams struct { type SellParams struct {
PayType int
Sum float64
Comment string
Name string Name string
Cnt int Cnt int
Price float64 Price float64
Disc float64 Disc float64
Taxgrp string Taxgrp string
CardParams CardParams Comment string
PayType int
CardParams *CardParams
Userinfo *api.Userinfo
} }
type CardParams struct { type CardParams struct {
Cardmask string Cardmask string
BankID string BankID string
RrnCode string RrnCode string
AuthCode string AuthCode string
} }
func NewVchasno(token string) *Vchasno { func (c *Client) NewSellParams() *SellParamsBuilder {
return &Vchasno{ return &SellParamsBuilder{
api: api.NewKasaInstance(token), client: c,
params: SellParams{
Name: c.defaults.ProductName,
Cnt: 1,
Taxgrp: c.defaults.Taxgrp,
Comment: c.defaults.Comment,
PayType: c.defaults.PayType,
},
} }
} }
func (v *Vchasno) NewSell(params SellParams) (*api.KasaResponse, error) { type SellParamsBuilder struct {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) client *Client
params SellParams
}
func (b *SellParamsBuilder) Name(name string) *SellParamsBuilder {
b.params.Name = name
return b
}
func (b *SellParamsBuilder) Cnt(cnt int) *SellParamsBuilder {
b.params.Cnt = cnt
return b
}
func (b *SellParamsBuilder) Price(price float64) *SellParamsBuilder {
b.params.Price = price
return b
}
func (b *SellParamsBuilder) Disc(disc float64) *SellParamsBuilder {
b.params.Disc = disc
return b
}
func (b *SellParamsBuilder) Taxgrp(taxgrp string) *SellParamsBuilder {
b.params.Taxgrp = taxgrp
return b
}
func (b *SellParamsBuilder) Comment(comment string) *SellParamsBuilder {
b.params.Comment = comment
return b
}
func (b *SellParamsBuilder) PayCash() *SellParamsBuilder {
b.params.PayType = api.PayTypeCash
b.params.CardParams = nil
return b
}
func (b *SellParamsBuilder) PayCard(cardmask, bankID, rrnCode, authCode string) *SellParamsBuilder {
b.params.PayType = api.PayTypeCard
b.params.CardParams = &CardParams{
Cardmask: cardmask,
BankID: bankID,
RrnCode: rrnCode,
AuthCode: authCode,
}
return b
}
func (b *SellParamsBuilder) Userinfo(email, phone string) *SellParamsBuilder {
b.params.Userinfo = &api.Userinfo{
Email: email,
Phone: phone,
}
return b
}
func (b *SellParamsBuilder) Build() SellParams {
return b.params
}
func (b *SellParamsBuilder) Execute(ctx context.Context) (*api.SellResponse, error) {
return b.client.Sell(ctx, b.params)
}
func (b *SellParamsBuilder) ExecuteWithTimeout(timeout time.Duration) (*api.SellResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
return v.api.NewSell(ctx, params.PayType, params.Sum, params.Comment, params.CardParams.Cardmask, params.CardParams.BankID, params.Name, params.Cnt, params.Price, params.Disc, params.Taxgrp, params.CardParams.RrnCode, params.CardParams.AuthCode) return b.client.Sell(ctx, b.params)
} }
func SetDefaultParams() SellParams { func (b *SellParamsBuilder) ExecuteDefault() (*api.SellResponse, error) {
return SellParams{ return b.ExecuteWithTimeout(b.client.defaults.DefaultTimeout)
PayType: PayTypeCard, }
Taxgrp: "1",
Cnt: 1, func (c *Client) Sell(ctx context.Context, params SellParams) (*api.SellResponse, error) {
} if params.Taxgrp == "" {
params.Taxgrp = c.defaults.Taxgrp
}
if params.Name == "" {
params.Name = c.defaults.ProductName
}
if params.Comment == "" {
params.Comment = c.defaults.Comment
}
if params.Cnt == 0 {
params.Cnt = 1
}
row := api.NewReceiptRow(params.Name, params.Cnt, params.Price, params.Taxgrp)
if params.Disc > 0 {
row.Disc = params.Disc
}
if params.Comment != "" {
row.Comment = params.Comment
}
var pay api.ReceiptPay
sum := (params.Price - params.Disc) * float64(params.Cnt)
if params.PayType == api.PayTypeCard && params.CardParams != nil {
pay = api.NewReceiptPayCard(
sum,
params.CardParams.Cardmask,
params.CardParams.BankID,
params.CardParams.RrnCode,
params.CardParams.AuthCode,
)
} else {
pay = api.NewReceiptPayCash(sum, params.Comment)
}
return c.api.Sell(ctx, api.SellParams{
Cashier: c.cashier,
Source: c.source,
Rows: []api.ReceiptRow{row},
Pays: []api.ReceiptPay{pay},
Userinfo: params.Userinfo,
})
}
func (c *Client) SellWithTimeout(params SellParams, timeout time.Duration) (*api.SellResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return c.Sell(ctx, params)
}
func (c *Client) QuickSell(ctx context.Context, price float64) (*api.SellResponse, error) {
return c.Sell(ctx, SellParams{
Price: price,
})
}
func (c *Client) QuickSellNamed(ctx context.Context, name string, price float64) (*api.SellResponse, error) {
return c.Sell(ctx, SellParams{
Name: name,
Price: price,
})
} }