agris 0.18.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/agents/app-explorer.md +212 -0
- data/.claude/agents/cross-review.md +152 -0
- data/.claude/agents/developer.md +215 -0
- data/.claude/agents/pm.md +401 -0
- data/.claude/agents/qa.md +235 -0
- data/.claude/agents/reviewer.md +185 -0
- data/.claude/agents/spec-preflight.md +167 -0
- data/.claude/agents/spec.md +244 -0
- data/.claude/ai/agent-rules.md +36 -0
- data/.claude/ai/spec-writing-guide.md +43 -0
- data/.claude/commands/app-explorer.md +87 -0
- data/.claude/commands/cross-review.md +91 -0
- data/.claude/commands/developer.md +118 -0
- data/.claude/commands/pm.md +166 -0
- data/.claude/commands/qa.md +111 -0
- data/.claude/commands/reviewer.md +86 -0
- data/.claude/commands/spec.md +172 -0
- data/.github/workflows/ci.yml +62 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +10 -103
- data/.ruby-version +1 -0
- data/CHANGELOG.md +79 -0
- data/CLAUDE.md +98 -0
- data/README.md +9 -1
- data/agris.gemspec +3 -2
- data/docs/RELEASING.md +147 -0
- data/docs/specs/README.md +26 -0
- data/docs/specs/accounts-payables/.gitkeep +0 -0
- data/docs/specs/accounts-receivables/.gitkeep +0 -0
- data/docs/specs/grain/.gitkeep +0 -0
- data/docs/specs/inventory/.gitkeep +0 -0
- data/docs/specs/messages/.gitkeep +0 -0
- data/docs/specs/rename-folder.example.md +82 -0
- data/docs/specs/spec-template.md +68 -0
- data/docs/specs/support/.gitkeep +0 -0
- data/lib/agris/api/accounts_payables/disbursements.rb +19 -0
- data/lib/agris/api/accounts_payables/new_disbursement.rb +42 -0
- data/lib/agris/api/accounts_payables.rb +3 -0
- data/lib/agris/api/accounts_receivables/invoices.rb +1 -1
- data/lib/agris/api/accounts_receivables/new_payment.rb +96 -0
- data/lib/agris/api/accounts_receivables/new_payment_remark.rb +38 -0
- data/lib/agris/api/accounts_receivables/payment_post_result.rb +35 -0
- data/lib/agris/api/accounts_receivables/payments.rb +33 -0
- data/lib/agris/api/accounts_receivables.rb +6 -0
- data/lib/agris/api/document_query_response.rb +6 -2
- data/lib/agris/api/grain/contract.rb +3 -3
- data/lib/agris/api/grain/new_ticket.rb +1 -0
- data/lib/agris/api/grain/purchase_contracts.rb +1 -1
- data/lib/agris/api/grain/sales_contracts.rb +1 -1
- data/lib/agris/api/grain.rb +4 -2
- data/lib/agris/api/inventory/delivery_tickets.rb +1 -1
- data/lib/agris/api/inventory/orders.rb +1 -1
- data/lib/agris/api/messages/changed_query_base.rb +1 -0
- data/lib/agris/api/messages/document_query_base.rb +1 -0
- data/lib/agris/api/messages/import.rb +1 -0
- data/lib/agris/api/name/new_farm_field.rb +5 -0
- data/lib/agris/api/new_voucher.rb +1 -3
- data/lib/agris/api/post_result.rb +9 -1
- data/lib/agris/client.rb +3 -1
- data/lib/agris/version.rb +1 -1
- data/lib/agris.rb +1 -0
- data/lib/hash.rb +1 -0
- metadata +48 -13
- data/.circleci/config.yml +0 -45
- data/.codeclimate.yml +0 -5
data/docs/RELEASING.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Releasing agris
|
|
2
|
+
|
|
3
|
+
The gem is published to [rubygems.org](https://rubygems.org/gems/agris) as `agris`.
|
|
4
|
+
|
|
5
|
+
Releases are cut with `bundle exec rake release`, which tags, pushes and
|
|
6
|
+
publishes in a single irreversible step. Read [What `rake release` actually
|
|
7
|
+
does](#what-rake-release-actually-does) before running it.
|
|
8
|
+
|
|
9
|
+
## Versioning
|
|
10
|
+
|
|
11
|
+
**One version line, released from `master`.** The version number follows
|
|
12
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) and describes the
|
|
13
|
+
**API** — not which Ruby the gem supports.
|
|
14
|
+
|
|
15
|
+
Ruby support is expressed in the gemspec instead:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
spec.required_ruby_version = '>= 3.1'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Bundler's resolver does the rest. An application on Ruby 3.1 resolves to the
|
|
22
|
+
newest version whose floor it satisfies; one on 3.4 resolves to the newest
|
|
23
|
+
version outright. There is no need for parallel release lines, and consumers do
|
|
24
|
+
not need to pin a version to stay on a supported Ruby.
|
|
25
|
+
|
|
26
|
+
> This replaced an earlier scheme that encoded the supported Ruby in the version
|
|
27
|
+
> number (`0.19.x` for Ruby 3.1, `0.2x` for Ruby 3.3). It fought semver — the
|
|
28
|
+
> version had to step *backwards* at one point — and RubyGems has no concept of
|
|
29
|
+
> release lines anyway: it always serves the highest version as `latest`.
|
|
30
|
+
|
|
31
|
+
### How Ruby changes map to versions
|
|
32
|
+
|
|
33
|
+
| Change | Version impact |
|
|
34
|
+
|--------|----------------|
|
|
35
|
+
| **Adding** support for a newer Ruby (3.5, 4.x) | **None.** Add it to the CI matrix. Patch release only if fixes were needed. |
|
|
36
|
+
| **Dropping** support for an older Ruby (raising `required_ruby_version`) | **Breaking** — major bump. |
|
|
37
|
+
|
|
38
|
+
Everything else is ordinary semver on the public API.
|
|
39
|
+
|
|
40
|
+
Raising the floor is breaking because applications that could install the gem
|
|
41
|
+
yesterday cannot today. Do it deliberately, in its own release.
|
|
42
|
+
|
|
43
|
+
### Keep the floor honest
|
|
44
|
+
|
|
45
|
+
`.github/workflows/ci.yml` runs the suite across **floor, current and next** —
|
|
46
|
+
currently 3.1, 3.3 and 3.4. If you change `required_ruby_version`, change the
|
|
47
|
+
matrix in the same PR, or the declared floor becomes a claim nothing tests.
|
|
48
|
+
|
|
49
|
+
Note that the matrix job names are required status checks on `master`
|
|
50
|
+
(`test (3.1)`, `test (3.3)`, `test (3.4)`, `lint`). Changing the matrix changes
|
|
51
|
+
those names, so branch protection must be updated to match — otherwise every
|
|
52
|
+
PR blocks on a check that can never report.
|
|
53
|
+
|
|
54
|
+
## Pre-release checklist
|
|
55
|
+
|
|
56
|
+
1. **Be on `master`**, up to date, with everything merged that you intend to
|
|
57
|
+
ship. `rake release` tags and pushes whatever branch you are standing on —
|
|
58
|
+
it does not check.
|
|
59
|
+
2. **Working tree is clean.** `rake release` aborts otherwise.
|
|
60
|
+
3. **Full suite is green:**
|
|
61
|
+
```sh
|
|
62
|
+
bundle exec rake # specs + rubocop
|
|
63
|
+
```
|
|
64
|
+
4. **Version is bumped** in `lib/agris/version.rb`, and the bump is committed.
|
|
65
|
+
5. **`required_ruby_version` is correct** for what you are shipping, and the CI
|
|
66
|
+
matrix covers it.
|
|
67
|
+
6. **The tag does not already exist:**
|
|
68
|
+
```sh
|
|
69
|
+
git tag --list 'v*' | sort -V | tail
|
|
70
|
+
```
|
|
71
|
+
7. **RubyGems credentials are present** — see [Credentials](#credentials).
|
|
72
|
+
|
|
73
|
+
## Cutting the release
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
git checkout master
|
|
77
|
+
git pull
|
|
78
|
+
# bump lib/agris/version.rb, commit, and merge via PR
|
|
79
|
+
bundle exec rake # confirm green
|
|
80
|
+
bundle exec rake release
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If your account has MFA enabled, `gem push` prompts for an OTP code partway
|
|
84
|
+
through — do not walk away from the terminal.
|
|
85
|
+
|
|
86
|
+
## What `rake release` actually does
|
|
87
|
+
|
|
88
|
+
Provided by `bundler/gem_tasks`. In order:
|
|
89
|
+
|
|
90
|
+
1. **Guards the working tree** — aborts with "There are files that need to be
|
|
91
|
+
committed first" if anything is modified or staged.
|
|
92
|
+
2. **Checks whether `v<version>` is already tagged** — if so, skips tagging and
|
|
93
|
+
continues to publish anyway.
|
|
94
|
+
3. **Creates the tag** — `git tag -m "Version <version>" v<version>`.
|
|
95
|
+
4. **Builds the gem** into `pkg/agris-<version>.gem`.
|
|
96
|
+
5. **Pushes to git** — the **current branch** and the new tag, to that branch's
|
|
97
|
+
configured remote (`origin` by default).
|
|
98
|
+
6. **Publishes to rubygems.org** — `gem push pkg/agris-<version>.gem`.
|
|
99
|
+
|
|
100
|
+
Steps 5 and 6 reach outside the repository. Step 6 is effectively permanent.
|
|
101
|
+
|
|
102
|
+
If tagging succeeds but a later step fails, the tag is removed automatically.
|
|
103
|
+
If the *push* succeeds and publishing fails, the tag is already public — delete
|
|
104
|
+
it manually before retrying:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
git tag -d v<version>
|
|
108
|
+
git push origin :refs/tags/v<version>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Credentials
|
|
112
|
+
|
|
113
|
+
`gem push` reads `~/.gem/credentials`. If you have never pushed from this
|
|
114
|
+
machine:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
gem signin
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The gemspec sets no `allowed_push_host`, so pushes go to rubygems.org. You must
|
|
121
|
+
be an owner of the `agris` gem — check with `gem owner agris`.
|
|
122
|
+
|
|
123
|
+
## After releasing
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
gem list agris --remote --all # new version is listed
|
|
127
|
+
git ls-remote --tags origin # tag reached the remote
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Confirm the published gem declares the Ruby floor you expect:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
gem specification agris -r required_ruby_version
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## If a release goes wrong
|
|
137
|
+
|
|
138
|
+
A published version can be yanked, but the version number is **burned
|
|
139
|
+
permanently** — it can never be reused:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
gem yank agris -v <version>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Yanking removes it from resolution for new installs; it does not recall it from
|
|
146
|
+
anyone who already has it. Prefer releasing a fixed higher version over yanking
|
|
147
|
+
wherever possible.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Specs
|
|
2
|
+
|
|
3
|
+
Source of truth for behavior in the `agris` gem. Implementation, tests, and PRs trace back to spec IDs (R#, E#, AC-#, AT#).
|
|
4
|
+
|
|
5
|
+
## How to add a spec
|
|
6
|
+
1. Copy `spec-template.md` into the appropriate area folder (see below).
|
|
7
|
+
2. Name the file by feature, kebab-case (e.g., `import-grain-ticket.md`).
|
|
8
|
+
3. Fill in Goal → Rules → Acceptance Criteria → Acceptance Tests.
|
|
9
|
+
4. Reference spec IDs in commits, tests, and PR descriptions.
|
|
10
|
+
|
|
11
|
+
## Layout
|
|
12
|
+
|
|
13
|
+
Specs are grouped by Agris API area, mirroring `lib/agris/api/`:
|
|
14
|
+
|
|
15
|
+
- `grain/` — tickets, contracts (purchase/sales), commodity codes, grade factors, rates
|
|
16
|
+
- `inventory/` — delivery tickets, orders
|
|
17
|
+
- `accounts-payables/` — voucher posting
|
|
18
|
+
- `accounts-receivables/` — invoice posting
|
|
19
|
+
- `messages/` — document-tracking and "changed since" queries
|
|
20
|
+
- `support/` — support / utility messages
|
|
21
|
+
|
|
22
|
+
Cross-cutting specs (config, transport, error model) can live at the top level of `docs/specs/`.
|
|
23
|
+
|
|
24
|
+
## Templates
|
|
25
|
+
- `spec-template.md` — blank template, copy this.
|
|
26
|
+
- `rename-folder.example.md` — completed example showing the expected level of detail.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Spec: Rename Folder (Example)
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Allow a user to rename an existing folder.
|
|
5
|
+
|
|
6
|
+
## Non Goals
|
|
7
|
+
- Moving folders
|
|
8
|
+
- Renaming multiple folders
|
|
9
|
+
|
|
10
|
+
## Definitions
|
|
11
|
+
- Folder: user-owned container for documents.
|
|
12
|
+
|
|
13
|
+
## Interfaces
|
|
14
|
+
Endpoint:
|
|
15
|
+
`PATCH /folders/{id}`
|
|
16
|
+
|
|
17
|
+
Input:
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"name": "string"
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Output:
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"id": "string",
|
|
28
|
+
"name": "string"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Rules
|
|
33
|
+
R1: Folder name must be 1-80 characters.
|
|
34
|
+
R2: Leading and trailing whitespace must be trimmed.
|
|
35
|
+
R3: If name is unchanged, return success without modification.
|
|
36
|
+
R4: User must have folder write permission.
|
|
37
|
+
|
|
38
|
+
## State Model
|
|
39
|
+
- Existing folder name -> renamed folder name (or unchanged for idempotent update).
|
|
40
|
+
|
|
41
|
+
## Edge Cases
|
|
42
|
+
E1: Folder does not exist -> return 404.
|
|
43
|
+
E2: Invalid name -> return 422.
|
|
44
|
+
E3: Unauthorized -> return 403.
|
|
45
|
+
|
|
46
|
+
## Acceptance Criteria
|
|
47
|
+
AC-1: User can rename an existing folder with a valid name (1-80 chars).
|
|
48
|
+
AC-2: System trims whitespace and handles idempotent renames.
|
|
49
|
+
AC-3: Unauthorized or invalid requests are rejected with appropriate status codes.
|
|
50
|
+
|
|
51
|
+
## Acceptance Tests
|
|
52
|
+
AT1
|
|
53
|
+
Given a valid folder id
|
|
54
|
+
When a valid name is submitted
|
|
55
|
+
Then the folder name is updated.
|
|
56
|
+
Covers: R1, R2
|
|
57
|
+
|
|
58
|
+
AT2
|
|
59
|
+
Given an unchanged folder name
|
|
60
|
+
When request is submitted
|
|
61
|
+
Then return success without modification.
|
|
62
|
+
Covers: R3
|
|
63
|
+
|
|
64
|
+
AT3
|
|
65
|
+
Given a user without permission
|
|
66
|
+
When request is submitted
|
|
67
|
+
Then return 403.
|
|
68
|
+
Covers: R4
|
|
69
|
+
|
|
70
|
+
## Observability
|
|
71
|
+
- Log validation failures with request id and rule ID.
|
|
72
|
+
- Emit metric for rename success/failure counts.
|
|
73
|
+
|
|
74
|
+
## Implementation Decisions
|
|
75
|
+
|
|
76
|
+
| Date | Decision | Rationale |
|
|
77
|
+
|------|----------|-----------|
|
|
78
|
+
|
|
79
|
+
## Change Log
|
|
80
|
+
|
|
81
|
+
| Date | Change | Affected IDs | Rationale |
|
|
82
|
+
|------|--------|-------------|-----------|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!-- See rename-folder.example.md for a completed example of this template -->
|
|
2
|
+
|
|
3
|
+
# Spec: <Feature Name>
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
Short description of the feature's purpose and value.
|
|
7
|
+
|
|
8
|
+
## Non Goals
|
|
9
|
+
Explicitly excluded behavior. List what this spec intentionally does NOT cover.
|
|
10
|
+
|
|
11
|
+
## Definitions
|
|
12
|
+
Key domain terms used in this spec. Define any term a new team member might misunderstand.
|
|
13
|
+
|
|
14
|
+
## Interfaces
|
|
15
|
+
Inputs, outputs, APIs, or UI events. Include request/response schemas where applicable.
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
<!-- Rules describe system behavior at implementation level.
|
|
19
|
+
Each rule should be specific enough to test.
|
|
20
|
+
Bad: "User can log in."
|
|
21
|
+
Good: "POST /api/auth/login with valid credentials returns 200 and a session token (R1)." -->
|
|
22
|
+
|
|
23
|
+
R1: <rule>
|
|
24
|
+
R2: <rule>
|
|
25
|
+
|
|
26
|
+
## State Model
|
|
27
|
+
If the system has state transitions, describe them here. Otherwise remove this section.
|
|
28
|
+
|
|
29
|
+
## Edge Cases
|
|
30
|
+
E1: <edge case and expected behavior>
|
|
31
|
+
E2: <edge case and expected behavior>
|
|
32
|
+
|
|
33
|
+
## Acceptance Criteria
|
|
34
|
+
<!-- ACs describe observable outcomes a stakeholder can verify.
|
|
35
|
+
Rules are implementation-facing; ACs are user-facing.
|
|
36
|
+
Example: "AC-1: User sees a green checkmark when password meets all strength requirements." -->
|
|
37
|
+
|
|
38
|
+
AC-1: <criterion>
|
|
39
|
+
AC-2: <criterion>
|
|
40
|
+
|
|
41
|
+
## Acceptance Tests
|
|
42
|
+
<!-- Each AT must have a Covers: field listing the R# or E# it validates.
|
|
43
|
+
An AT can cover multiple rules. -->
|
|
44
|
+
|
|
45
|
+
AT1
|
|
46
|
+
Given <context>
|
|
47
|
+
When <action>
|
|
48
|
+
Then <outcome>
|
|
49
|
+
Covers: R1
|
|
50
|
+
|
|
51
|
+
AT2
|
|
52
|
+
Given <context>
|
|
53
|
+
When <action>
|
|
54
|
+
Then <outcome>
|
|
55
|
+
Covers: R2
|
|
56
|
+
|
|
57
|
+
## Observability
|
|
58
|
+
Logs, metrics, alerts (optional).
|
|
59
|
+
|
|
60
|
+
## Implementation Decisions
|
|
61
|
+
|
|
62
|
+
| Date | Decision | Rationale |
|
|
63
|
+
|------|----------|-----------|
|
|
64
|
+
|
|
65
|
+
## Change Log
|
|
66
|
+
|
|
67
|
+
| Date | Change | Affected IDs | Rationale |
|
|
68
|
+
|------|--------|-------------|-----------|
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsPayables
|
|
6
|
+
module Disbursements
|
|
7
|
+
def create_disbursement(disbursement)
|
|
8
|
+
response = @request.process_message(
|
|
9
|
+
Gyoku.xml(xml: context_hash),
|
|
10
|
+
82_320,
|
|
11
|
+
create_post_payload_xml([disbursement.to_xml_hash])
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
PostResult.new(response)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsPayables
|
|
6
|
+
class NewDisbursement
|
|
7
|
+
include XmlModel
|
|
8
|
+
|
|
9
|
+
ATTRIBUTE_NAMES = %w(
|
|
10
|
+
applied_date
|
|
11
|
+
apply_payment_on_account
|
|
12
|
+
bank_code
|
|
13
|
+
cash_source
|
|
14
|
+
check_location
|
|
15
|
+
check_number
|
|
16
|
+
currency_code
|
|
17
|
+
discount_amount
|
|
18
|
+
exchange_rate
|
|
19
|
+
exchange_rate_date
|
|
20
|
+
misc_reference
|
|
21
|
+
name_id
|
|
22
|
+
payment_amount
|
|
23
|
+
payment_date
|
|
24
|
+
prepayment_reference
|
|
25
|
+
prepayment_type
|
|
26
|
+
record_type
|
|
27
|
+
reserved
|
|
28
|
+
voucher_location
|
|
29
|
+
voucher_number
|
|
30
|
+
).freeze
|
|
31
|
+
|
|
32
|
+
attr_reader(*ATTRIBUTE_NAMES)
|
|
33
|
+
|
|
34
|
+
def initialize(hash = {})
|
|
35
|
+
super
|
|
36
|
+
|
|
37
|
+
@record_type = 'ACPD0'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module Agris
|
|
4
4
|
module Api
|
|
5
5
|
module AccountsPayables
|
|
6
|
+
autoload :Disbursements, 'agris/api/accounts_payables/disbursements'
|
|
7
|
+
autoload :NewDisbursement, 'agris/api/accounts_payables/new_disbursement'
|
|
8
|
+
|
|
6
9
|
module Vouchers
|
|
7
10
|
def create_voucher(voucher)
|
|
8
11
|
response = @request.process_message(
|
|
@@ -17,7 +17,7 @@ module Agris
|
|
|
17
17
|
)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def invoices_changed_since(datetime, detail
|
|
20
|
+
def invoices_changed_since(datetime, detail: false)
|
|
21
21
|
extract_documents(
|
|
22
22
|
Messages::QueryChangedInvoices.new(datetime, detail),
|
|
23
23
|
Agris::Api::AccountsReceivables::Invoice
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsReceivables
|
|
6
|
+
class NewPayment
|
|
7
|
+
include XmlModel
|
|
8
|
+
|
|
9
|
+
RECEIVE_ON_ACCOUNT = '0'
|
|
10
|
+
APPLY_TO_INVOICE = 'E'
|
|
11
|
+
|
|
12
|
+
ATTRIBUTE_NAMES = %w(
|
|
13
|
+
applied_date
|
|
14
|
+
apply_receive_on_acct
|
|
15
|
+
bank_code
|
|
16
|
+
cash_source
|
|
17
|
+
check_number
|
|
18
|
+
discount_amount
|
|
19
|
+
invoice_line_pricing_no
|
|
20
|
+
invoice_location
|
|
21
|
+
name_id
|
|
22
|
+
payment_amount
|
|
23
|
+
payment_date
|
|
24
|
+
receipt_location
|
|
25
|
+
receipt_number
|
|
26
|
+
record_type
|
|
27
|
+
).freeze
|
|
28
|
+
|
|
29
|
+
attr_reader(*ATTRIBUTE_NAMES)
|
|
30
|
+
|
|
31
|
+
# Agris allocates the receipt number, so it is left for the response.
|
|
32
|
+
# An applied date or invoice on an unapplied receipt is rejected.
|
|
33
|
+
def self.receive(attributes = {})
|
|
34
|
+
new(
|
|
35
|
+
attributes.merge(
|
|
36
|
+
apply_receive_on_acct: RECEIVE_ON_ACCOUNT,
|
|
37
|
+
applied_date: '',
|
|
38
|
+
invoice_line_pricing_no: ''
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Draws down the unapplied balance of a receipt Agris already
|
|
44
|
+
# allocated; it cannot create one.
|
|
45
|
+
def self.apply(attributes = {})
|
|
46
|
+
new(attributes.merge(apply_receive_on_acct: APPLY_TO_INVOICE))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def initialize(attributes = {})
|
|
50
|
+
super
|
|
51
|
+
|
|
52
|
+
@record_type = 'ACRR0'
|
|
53
|
+
@remarks = []
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_remark(remark)
|
|
57
|
+
@remarks << remark
|
|
58
|
+
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def remarks
|
|
63
|
+
@remarks || []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def records
|
|
67
|
+
[self] + remarks
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def for_allocation(allocation)
|
|
71
|
+
allocated = self.class.new(hash.merge(allocation_hash(allocation)))
|
|
72
|
+
|
|
73
|
+
remarks.each do |remark|
|
|
74
|
+
allocated.add_remark(remark.for_allocation(allocation))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
allocated
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def xml_ignore_attributes
|
|
81
|
+
[:remarks]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
protected
|
|
85
|
+
|
|
86
|
+
def allocation_hash(allocation)
|
|
87
|
+
{
|
|
88
|
+
bank_code: allocation.bank_code,
|
|
89
|
+
receipt_location: allocation.receipt_location,
|
|
90
|
+
receipt_number: allocation.receipt_number
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsReceivables
|
|
6
|
+
class NewPaymentRemark
|
|
7
|
+
include XmlModel
|
|
8
|
+
|
|
9
|
+
ATTRIBUTE_NAMES = %w(
|
|
10
|
+
bank_code
|
|
11
|
+
receipt_location
|
|
12
|
+
receipt_number
|
|
13
|
+
record_type
|
|
14
|
+
remark_number
|
|
15
|
+
remark_value
|
|
16
|
+
).freeze
|
|
17
|
+
|
|
18
|
+
attr_reader(*ATTRIBUTE_NAMES)
|
|
19
|
+
|
|
20
|
+
def initialize(attributes = {})
|
|
21
|
+
super
|
|
22
|
+
|
|
23
|
+
@record_type = 'ACRR1'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def for_allocation(allocation)
|
|
27
|
+
self.class.new(
|
|
28
|
+
hash.merge(
|
|
29
|
+
bank_code: allocation.bank_code,
|
|
30
|
+
receipt_location: allocation.receipt_location,
|
|
31
|
+
receipt_number: allocation.receipt_number
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsReceivables
|
|
6
|
+
# The posted receipt comes back in `document` as a fixed-width triple:
|
|
7
|
+
# receipt location (3), receipt number (6), bank code (2).
|
|
8
|
+
class PaymentPostResult < PostResult
|
|
9
|
+
def receipt_location
|
|
10
|
+
document_field(0, 3)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def receipt_number
|
|
14
|
+
document_field(3, 6)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def bank_code
|
|
18
|
+
document_field(9, 2)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# A rejected post still returns the location and bank code, so only the
|
|
22
|
+
# receipt number tells you whether Agris allocated a receipt.
|
|
23
|
+
def allocated?
|
|
24
|
+
!receipt_number.empty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def document_field(offset, length)
|
|
30
|
+
document_number.to_s[offset, length].to_s.strip
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agris
|
|
4
|
+
module Api
|
|
5
|
+
module AccountsReceivables
|
|
6
|
+
module Payments
|
|
7
|
+
def create_payment(payment)
|
|
8
|
+
post_payment(payment)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Agris will not accept a receipt and its applications in one message,
|
|
12
|
+
# so each application posts against the receipt Agris allocated.
|
|
13
|
+
def apply_payment(payment, allocation)
|
|
14
|
+
post_payment(payment.for_allocation(allocation))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
protected
|
|
18
|
+
|
|
19
|
+
def post_payment(payment)
|
|
20
|
+
message = Messages::Import.new(payment)
|
|
21
|
+
|
|
22
|
+
response = @request.process_message(
|
|
23
|
+
Gyoku.xml(xml: context_hash),
|
|
24
|
+
message.message_number,
|
|
25
|
+
message.to_xml
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
PaymentPostResult.new(response)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -4,6 +4,12 @@ module Agris
|
|
|
4
4
|
module AccountsReceivables
|
|
5
5
|
autoload :Invoice, 'agris/api/accounts_receivables/invoice'
|
|
6
6
|
autoload :Invoices, 'agris/api/accounts_receivables/invoices'
|
|
7
|
+
autoload :NewPayment, 'agris/api/accounts_receivables/new_payment'
|
|
8
|
+
autoload :NewPaymentRemark,
|
|
9
|
+
'agris/api/accounts_receivables/new_payment_remark'
|
|
10
|
+
autoload :PaymentPostResult,
|
|
11
|
+
'agris/api/accounts_receivables/payment_post_result'
|
|
12
|
+
autoload :Payments, 'agris/api/accounts_receivables/payments'
|
|
7
13
|
autoload :SpecificInvoiceExtract,
|
|
8
14
|
'agris/api/accounts_receivables/specific_invoice_extract'
|
|
9
15
|
end
|
|
@@ -9,7 +9,8 @@ module Agris
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def last_request_date_time
|
|
12
|
-
|
|
12
|
+
system_hash = @output_hash[pluralized_resource_name]['system']
|
|
13
|
+
Time.parse(system_hash['lastrequestdatetime'])
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def documents
|
|
@@ -37,7 +38,10 @@ module Agris
|
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def resource_type
|
|
40
|
-
@resource_type ||=
|
|
41
|
+
@resource_type ||= begin
|
|
42
|
+
name = self.class.name.split('::').last.chomp('ExtractResponse')
|
|
43
|
+
Object.const_get(name)
|
|
44
|
+
end
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
def resources
|
|
@@ -104,11 +104,11 @@ module Agris
|
|
|
104
104
|
def self.add_transcodes_to_schedule(schedule)
|
|
105
105
|
if schedule['trancodes']
|
|
106
106
|
trancodes = schedule['trancodes']['trancode']
|
|
107
|
-
trancodes = [trancodes] unless trancodes.
|
|
107
|
+
trancodes = [trancodes] unless trancodes.instance_of?(Array)
|
|
108
108
|
trancodes.each do |trancode|
|
|
109
|
-
label_code = trancode['label'].downcase
|
|
109
|
+
label_code = "#{trancode['label'].downcase}code"
|
|
110
110
|
schedule[label_code] = trancode['code']
|
|
111
|
-
label_description = trancode['label'].downcase
|
|
111
|
+
label_description = "#{trancode['label'].downcase}description"
|
|
112
112
|
schedule[label_description] = trancode['description']
|
|
113
113
|
end
|
|
114
114
|
end
|