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
go get git.jeezft.xyz/rk/go-vchasno-kassa
go get gitea.jeezft.xyz/jeezft/go-vchasno-kassa
```
## Быстрый старт
### Базовое использование
```go
package main
import (
"context"
"log"
"time"
"git.jeezft.xyz/rk/go-vchasno-kassa"
"gitea.jeezft.xyz/jeezft/go-vchasno-kassa"
)
func main() {
client := vchasno.NewClient(vchasno.Config{
Token: "your-api-token-here",
Timeout: 30 * time.Second,
})
ctx := context.Background()
// Проверка подключения
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)
response, err := client.QuickSell(ctx, 100.00)
if err != nil {
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/`.
```bash
cd examples
go run main.go
```
## Валидация
SDK включает встроенную валидацию фискальных чеков:
### 1. Быстрая продажа (QuickSell)
```go
receipt := vchasno.FiscalReceipt{
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)
}
response, err := client.QuickSell(ctx, 100.00)
```
## Обработка ошибок
Использует все дефолтные параметры из конфигурации.
### 2. Быстрая продажа с названием
```go
response, err := client.CreateFiscalReceipt(ctx, receipt)
if err != nil {
switch {
case errors.Is(err, vchasno.ErrMissingToken):
log.Println("Отсутствует токен авторизации")
default:
log.Printf("API error: %v", err)
}
}
response, err := client.QuickSellNamed(ctx, "Парковка VIP", 150.00)
```
## Работа с деньгами
SDK использует копейки для точных денежных расчетов:
### 3. Builder Pattern - базовый пример
```go
// Создание суммы в копейках
price := vchasno.NewMoney(10.50) // 1050 копеек
// Конвертация обратно в гривны
amount := price.ToFloat64() // 10.50
// Создание налога
tax := vchasno.NewTax(4.0, vchasno.NewMoney(0.42)) // 4% НДС
response, err := client.NewSellParams().
Price(100.00).
ExecuteDefault()
```
## Типы операций
Через эндпоинт `/api/v3/fiscal/execute` выполняются следующие команды:
- `create_receipt` - создание фискального чека
- `get_receipt` - получение информации о чеке
- `cancel_receipt` - отмена чека
- `x_report` - получение X-отчета
- `z_report` - получение Z-отчета
- `ping` - проверка соединения
## Конфигурация
### 4. Builder Pattern - полный пример
```go
client := vchasno.NewClient(vchasno.Config{
BaseURL: "https://kasa.vchasno.ua", // необязательно, по умолчанию
Token: "your-token", // обязательно
Timeout: 30 * time.Second, // необязательно
HTTPClient: &http.Client{...}, // необязательно
response, err := client.NewSellParams().
Name("Парковка premium").
Price(200.00).
Cnt(2).
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 ВЧАСНО доступна по адресу: 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"
)
type Vchasno struct {
api *api.Kasa
type Client struct {
api *api.Client
cashier string
source string
defaults *DefaultParams
}
const (
PayTypeCash = 0
PayTypeCard = 2
)
type Config struct {
Token string
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 {
PayType int
Sum float64
Comment string
Name string
Cnt int
Price float64
Disc float64
Taxgrp string
CardParams CardParams
Comment string
PayType int
CardParams *CardParams
Userinfo *api.Userinfo
}
type CardParams struct {
Cardmask string
BankID string
RrnCode string
AuthCode string
}
func NewVchasno(token string) *Vchasno {
return &Vchasno{
api: api.NewKasaInstance(token),
func (c *Client) NewSellParams() *SellParamsBuilder {
return &SellParamsBuilder{
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) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
type SellParamsBuilder struct {
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()
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 {
return SellParams{
PayType: PayTypeCard,
Taxgrp: "1",
Cnt: 1,
}
func (b *SellParamsBuilder) ExecuteDefault() (*api.SellResponse, error) {
return b.ExecuteWithTimeout(b.client.defaults.DefaultTimeout)
}
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,
})
}